65.9K
CodeProject 正在变化。 阅读更多。
Home

“灯柱”

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2018年6月20日

CPOL

7分钟阅读

viewsIcon

9618

downloadIcon

185

从未实现的 RFID 读取器想法...

引言

我的姐夫有一个关于如何管理机场行李的想法,使用简单且便宜的射频识别 (RFID) 贴纸。在这篇文章中,我将展示我是如何为这个想法创建一个原型。

由于原型的形状,我们决定称它为“Lampost”。

背景

自2010年以来,机场行李丢失的数量急剧下降,这在很大程度上归因于行李处理新技术的引入。行李丢失率曾低至5%,但如果你需要转机,行李被误投的几率可能高达40%甚至更高。去年,国际航空运输协会 (IATA) 开始研究 RFID 技术在改善这种情况方面的可能用途。根据他们的研究,每位乘客的成本为0.10美元,总体节省至少为0.20美元。

原因是 RFID 非常便宜、灵活,并且不需要客户维护。无源 RFID 只是一个简单的贴纸,可以粘贴、缝制或植入行李上。它不需要电池或其他电源来保持激活状态。

所以总体的想法是:

  • 所有行李在值机时都会贴上 RFID 贴纸。
  • 行李运输的任何地方都会安装固定的 RFID 读取器。对于无法安装固定读取器的地方,还会配备移动读取器。
  • 每个读取器都会将其读取到的每个物品报告给中央服务器应用程序,该应用程序将跟踪物品的位置。
  • 这将包括行李从飞机卸载并运往航站楼,以及装载到转盘上的过程。

硬件

项目的核心是一个 RedBoard,它是 Arduino Uno 的一个带有 mini USB 的版本。我将其与一个 WiFi 扩展板配对,该扩展板由 ESP8266 供电,并配有一个 2.4 GHz 天线。为了在我焊接原型时省点力气,我使用了一些堆叠式排针将它们连接在一起。

RedBoard、WiFi 扩展板堆叠在一起。图片由 SparkFun Electronics 提供,摄影师为 Juan Peña。

解决方案的下一层是 RFID 读取器模块。我选择了 ThingMagicm 制造的 Micro 模块之一,它符合我们的要求。这个模块附带自己的电路板,但与 Arduino 的布局不兼容,所以(为了省点焊接工夫)我买了一个 Arduino Proto Shield,并请一位当地专业人士将 RFID 读取器模块焊接到上面(它有开放式边缘——意味着边缘焊接,所以我更喜欢那样)。然后我使用一些堆叠式排针将其作为第三层添加。最后一步是将天线连接到 RFID 模块。

RFID 读取器模块。图片由 atlasRFIDstore 提供。

设置

WiFi 扩展板可以使用 UART 通过引脚 0/1(硬件)或 8/9(软件)与 Arduino 板通信。它可以通过板载开关进行配置。我选择了软件端口,这样 0/1 对就可以留空,以防我需要更多没有这种灵活性的硬件。RFID 模块可以以多种方式工作(包括直接连接到 PC 进行逐步编程)。在这个项目中,我将其配置为在自主模式下工作。为此,我将其连接到 PC,并使用了设备自带的实用软件。在此模式下,模块上电后会开始扫描 RFID 标签,并在识别到标签时通过 UART 提供数据。

焊接时,我将 RFID 模块的 TX/RX 线连接到 Arduino 的 10/11 引脚,以将 0/1 引脚留空,原因同上。

此时,我只需要给 Arduino 供电,硬件部分就完成了。

软件

软件解决方案有三个部分。Arduino 负责配置“Lampost”单元,监听 RFID 模块,并在识别到标签时调用 Web 服务。Web 服务用于接收来自 Arduino 的标签数据,并执行所有相关的检查和通知。还有一个简单的桌面应用程序,可用于与“Lampost”通信并进行配置。

对于 Arduino 部分,我使用了 Arduino IDE,而对于 Web 服务和配置工具,我使用了 Visual Studio。

Arduino - 配置

Arduino 程序这部分处理来自用于配置单元本身的桌面应用程序的传入命令。此配置用于设置“Lampost”的网络信息和标识。信息存储在 Arduino 的 EEPROM 内存中,因此即使在断电或其他重启后,模块也可以连接到网络。

#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 - WiFi 扩展板

现在我们有了模块用于识别自身并与 Web 服务通信所需的所有信息,因此我们可以开始通信了。但首先,我们需要加载适用于 WiFi 扩展板的库。该库(zip 格式)可以在这里获得:SparkFun_ESP8266_AT_Arduino_Library,而将其安装到 Arduino IDE 的文档在这里:Arduino Libraries

新添加的部分用粗体表示(为了保持简洁,一些旧的部分被省略了)。如您所见,我将 WiFi 扩展板置于 station 模式,因为我只想发送数据,而不监听传入流量。此时,没有任何数据被发送出去,但如果您检查网络,您可以看到该模块已正常运行。

#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 - RFID 模块

RFID 模块处于自主模式,这意味着它不会等待我命令它扫描标签,而是会自动扫描,并将信息收集到其内部缓冲区中。所以我只需要打开另一个串行端口并读取该内部缓冲区。由于我选择为我的原型使用一种特定的标签(标准 96 位信息),我只需要读取 12 字节的信息并将其发送到 Web 服务器。

在本节代码中,有大量与 RFID 模块相关的工作,这些工作基于随附的文档和代码示例。我不会详细说明原因——如果您打算使用此类模块,您需要自己去学习。

// 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();
}

桌面配置工具

现在“Lampost”已经启动并运行,我们应该对其进行配置,以便 WiFi 连接能够真正地将数据发送到 Web 服务器(RFID-Arduino 部分可以独立工作,但 Arduino-WiFi 部分需要信息)。

这是一个相当简单的应用程序,它所做的就是扫描 COM 端口以查找已连接的设备。如果找到设备,它将显示设备上的当前信息,并允许用户设置新值。

缺失的部分

为了能够保存多个“Lampost”的信息,可以考虑将此应用程序的信息发送到中央数据库(也许按 MAC 地址索引),这样就可以创建一个“Lampost”的物理地图。这将对于让乘客了解他们的行李的去向至关重要。

在一个更复杂的解决方案中,连接到 Arduino 的 WiFi 将同时用作服务器和客户端,这将允许通过网络进行配置。

Web服务

在这里我无法展示大部分代码,因为其中很多都非常依赖于具体实现(并且绝对与 Arduino 无关)。我可以向您展示一个 C# 框架,说明如何接收 RFID 数据。

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
        }
    }
}

摘要

如我上面所说,这不是一个实时项目,而是一个概念验证原型。实际原型演示了 Arduino 如何用于创建一些相当复杂的硬件,这些硬件可以从/向不同源接收和传输数据。此类硬件——与适当的后端软件(而不是我编写的)配对——可用于改善许多领域的服务;您只需要一个正确的想法……

历史

  • 2018 年 6 月 20 日:初始版本
© . All rights reserved.