“灯柱”





5.00/5 (3投票s)
从未实现的 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 天线。为了在我焊接原型时省点力气,我使用了一些堆叠式排针将它们连接在一起。
![]() | ![]() | ![]() |
解决方案的下一层是 RFID 读取器模块。我选择了 ThingMagicm 制造的 Micro 模块之一,它符合我们的要求。这个模块附带自己的电路板,但与 Arduino 的布局不兼容,所以(为了省点焊接工夫)我买了一个 Arduino Proto Shield,并请一位当地专业人士将 RFID 读取器模块焊接到上面(它有开放式边缘——意味着边缘焊接,所以我更喜欢那样)。然后我使用一些堆叠式排针将其作为第三层添加。最后一步是将天线连接到 RFID 模块。
设置
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 日:初始版本