From 6793c892ed68a796bf9e61c1acb7414a85da4a44 Mon Sep 17 00:00:00 2001 From: Jon Trulson Date: Wed, 2 Sep 2015 16:12:19 -0600 Subject: [PATCH] sm130: Complete rewrite This is a rewrite of the existing SM130 driver which was incomplete and non-functional. It was implemented using a Sparkfun SM130 module: https://www.sparkfun.com/products/10126 ... using a Sparkfun RFID Evaluation Shield: https://www.sparkfun.com/products/10406 It operates in UART mode only. A port to support I2C communications (requires a encrypted firmware reflash from SonMicro) should be fairly trivial, if you have one of those. Signed-off-by: Jon Trulson Signed-off-by: sisinty sasmita patra Signed-off-by: Mihai Tudor Panu --- examples/c++/CMakeLists.txt | 3 + examples/c++/sm130.cxx | 74 +++ examples/javascript/sm130.js | 70 +++ examples/python/sm130.py | 63 +++ src/sm130/sm130.cxx | 1031 +++++++++++++++++++++++++++------- src/sm130/sm130.h | 543 ++++++++++++++---- 6 files changed, 1451 insertions(+), 333 deletions(-) create mode 100644 examples/c++/sm130.cxx create mode 100644 examples/javascript/sm130.js create mode 100644 examples/python/sm130.py diff --git a/examples/c++/CMakeLists.txt b/examples/c++/CMakeLists.txt index 392157e6..e7f4e8e2 100644 --- a/examples/c++/CMakeLists.txt +++ b/examples/c++/CMakeLists.txt @@ -137,6 +137,7 @@ add_executable (mpu9250-example mpu9250.cxx) add_executable (hyld9767-example hyld9767.cxx) add_executable (mg811-example mg811.cxx) add_executable (wheelencoder-example wheelencoder.cxx) +add_executable (sm130-example sm130.cxx) include_directories (${PROJECT_SOURCE_DIR}/src/hmc5883l) include_directories (${PROJECT_SOURCE_DIR}/src/grove) @@ -246,6 +247,7 @@ include_directories (${PROJECT_SOURCE_DIR}/src/lsm9ds0) include_directories (${PROJECT_SOURCE_DIR}/src/hyld9767) include_directories (${PROJECT_SOURCE_DIR}/src/mg811) include_directories (${PROJECT_SOURCE_DIR}/src/wheelencoder) +include_directories (${PROJECT_SOURCE_DIR}/src/sm130) target_link_libraries (hmc5883l-example hmc5883l ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries (groveled-example grove ${CMAKE_THREAD_LIBS_INIT}) @@ -384,3 +386,4 @@ target_link_libraries (mpu9250-example mpu9150 ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries (hyld9767-example hyld9767 ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries (mg811-example mg811 ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries (wheelencoder-example wheelencoder ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries (sm130-example sm130 ${CMAKE_THREAD_LIBS_INIT}) diff --git a/examples/c++/sm130.cxx b/examples/c++/sm130.cxx new file mode 100644 index 00000000..3d1e3bdb --- /dev/null +++ b/examples/c++/sm130.cxx @@ -0,0 +1,74 @@ +/* + * Author: Jon Trulson + * Copyright (c) 2015 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include "sm130.h" + +using namespace std; +using namespace upm; + +int main (int argc, char **argv) +{ +//! [Interesting] + + // Instantiate a UART based SM130 RFID Module using defaults + upm::SM130* sensor = new upm::SM130(); + + // set the baud rate. 19200 baud is the default. + if (sensor->setBaudRate(19200)) + { + cerr << "Failed to set baud rate" << endl; + return 1; + } + + cout << "Resetting..." << endl; + sensor->reset(); + + cout << "Firmware revision: " << sensor->getFirmwareVersion() << endl; + + cout << "Waiting up to 5 seconds for a tag..." << endl; + + if (sensor->waitForTag(5000)) + { + cout << "Found tag, UID: " + << sensor->string2HexString(sensor->getUID()) << endl; + cout << "Tag Type: " << sensor->tag2String(sensor->getTagType()) + << endl; + } + else + { + // error + cout << "waitForTag failed: " << sensor->getLastErrorString() << endl; + } + +//! [Interesting] + + cout << "Exiting" << endl; + delete sensor; + return 0; +} diff --git a/examples/javascript/sm130.js b/examples/javascript/sm130.js new file mode 100644 index 00000000..b0f86f84 --- /dev/null +++ b/examples/javascript/sm130.js @@ -0,0 +1,70 @@ +/*jslint node:true, vars:true, bitwise:true, unparam:true */ +/*jshint unused:true */ + +/* + * Author: Jon Trulson + * Copyright (c) 2015 Intel Corporation. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +var sensorObj = require('jsupm_sm130'); + +// Instantiate a UART based SM130 RFID Module using defaults +var sensor = new sensorObj.SM130(); + +// Set the baud rate, 19200 baud is the default. +if (sensor.setBaudRate(19200)) +{ + console.log("Failed to set baud rate"); + process.exit(0); +} + +console.log("Resetting..."); +sensor.reset(); + +console.log("Firmware revision: " + sensor.getFirmwareVersion()); + +console.log("Waiting up to 5 seconds for a tag..."); + +if (sensor.waitForTag(5000)) +{ + console.log("Found tag, UID: " + + sensor.string2HexString(sensor.getUID())); + console.log("Tag Type: " + + sensor.tag2String(sensor.getTagType())); +} +else +{ + // error + console.log("waitForTag failed: " + + sensor.getLastErrorString()); +} + +/************** Exit code **************/ +process.on('SIGINT', function() +{ + sensor = null; + sensorObj.cleanUp(); + sensorObj = null; + console.log("Exiting..."); + process.exit(0); +}); diff --git a/examples/python/sm130.py b/examples/python/sm130.py new file mode 100644 index 00000000..ce6ae1bc --- /dev/null +++ b/examples/python/sm130.py @@ -0,0 +1,63 @@ +#!/usr/bin/python +# Author: Jon Trulson +# Copyright (c) 2015 Intel Corporation. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import time, sys, signal, atexit +import pyupm_sm130 as sensorObj + +# Instantiate a UART based SM130 RFID Module using defaults +sensor = sensorObj.SM130() + +## Exit handlers ## +# This stops python from printing a stacktrace when you hit control-C +def SIGINTHandler(signum, frame): + raise SystemExit + +# This function lets you run code on exit +def exitHandler(): + print "Exiting" + sys.exit(0) + +# Register exit handlers +atexit.register(exitHandler) +signal.signal(signal.SIGINT, SIGINTHandler) + +# Set the baud rate, 19200 baud is the default. +if (sensor.setBaudRate(19200)): + print "Failed to set baud rate" + sys.exit(0) + +print "Resetting..." +sensor.reset() + +print "Firmware revision: " + sensor.getFirmwareVersion() + +print "Waiting up to 5 seconds for a tag..." + +if (sensor.waitForTag(5000)): + print "Found tag, UID:", + print sensor.string2HexString(sensor.getUID()) + print "Tag Type:", + print sensor.tag2String(sensor.getTagType()) +else: + # error + print "waitForTag failed: " + sensor.getLastErrorString() diff --git a/src/sm130/sm130.cxx b/src/sm130/sm130.cxx index c15a1bb8..d4ba401b 100644 --- a/src/sm130/sm130.cxx +++ b/src/sm130/sm130.cxx @@ -1,6 +1,6 @@ /* - * Author: Yevgeniy Kiveisha - * Copyright (c) 2014 Intel Corporation. + * Author: Jon Trulson + * Copyright (c) 2015 Intel Corporation. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -22,248 +22,857 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include -#include -#include -#include #include #include "sm130.h" using namespace upm; +using namespace std; -SM130::SM130 (int bus, int devAddr, int rst, int dready) { - mraa_result_t error = MRAA_SUCCESS; +// Uncomment the below to see packaet data sent and received from the SM130 +// #define SM130_DEBUG - m_name = "SM130"; +static const int defaultDelay = 1000; // ms for read - m_i2cAddr = devAddr; - m_bus = bus; +static const int maxLen = 64; // max number of bytes to read - if (!(m_i2Ctx = mraa_i2c_init(m_bus))) - { - throw std::invalid_argument(std::string(__FUNCTION__) + - ": mraa_i2c_init() failed"); - } +SM130::SM130(int uart, int reset) : + m_uart(uart), m_gpioReset(reset) +{ + m_tagType = TAG_NONE; + m_uidLen = 0; + m_uid.clear(); + clearError(); + initClock(); - mraa_result_t ret = mraa_i2c_address(m_i2Ctx, m_i2cAddr); - if (ret != MRAA_SUCCESS) { - throw std::invalid_argument(std::string(__FUNCTION__) + - ": mraa_i2c_address() failed"); - } - - m_resetPinCtx = mraa_gpio_init (rst); - if (m_resetPinCtx == NULL) { - throw std::invalid_argument(std::string(__FUNCTION__) + - ": mraa_gpio_init(RESET) failed"); - } - - m_dataReadyPinCtx = mraa_gpio_init (dready); - if (m_dataReadyPinCtx == NULL) { - throw std::invalid_argument(std::string(__FUNCTION__) + - ": mraa_gpio_init(DATA READY) failed"); - } - - error = mraa_gpio_dir (m_resetPinCtx, MRAA_GPIO_OUT); - if (error != MRAA_SUCCESS) { - throw std::invalid_argument(std::string(__FUNCTION__) + - ": mraa_gpio_dir(RESET) failed"); - } - - error = mraa_gpio_dir (m_dataReadyPinCtx, MRAA_GPIO_OUT); - if (error != MRAA_SUCCESS) { - throw std::invalid_argument(std::string(__FUNCTION__) + - ": mraa_gpio_dir(DATA READY) failed"); - } + m_gpioReset.dir(mraa::DIR_OUT); + m_gpioReset.write(0); } -SM130::~SM130 () { - mraa_result_t error = MRAA_SUCCESS; - - error = mraa_i2c_stop(m_i2Ctx); - if (error != MRAA_SUCCESS) { - mraa_result_print(error); - } - error = mraa_gpio_close (m_resetPinCtx); - if (error != MRAA_SUCCESS) { - mraa_result_print(error); - } - error = mraa_gpio_close (m_dataReadyPinCtx); - if (error != MRAA_SUCCESS) { - mraa_result_print(error); - } +SM130::~SM130() +{ } -const char* -SM130::getFirmwareVersion () { - // Send VERSION command and retry a few times if no response - for (uint8_t n = 0; n < 10; n++) { - sendCommand (CMD_VERSION); - if (available() && getCommand() == CMD_VERSION) - // return versionString; - usleep(100 * 1000); - } - - return 0; +mraa_result_t SM130::setBaudRate(int baud) +{ + m_baud = baud; + return m_uart.setBaudRate(baud); } -uint8_t -SM130::available () { - // If in SEEK mode and using DREADY pin, check the status - if (m_LastCMD == CMD_SEEK_TAG) { - if (!mraa_gpio_read(m_dataReadyPinCtx)) { - return false; - } - } +string SM130::sendCommand(CMD_T cmd, string data) +{ + uint8_t cksum = 0; + string command; - // Set the maximum length of the expected response packet - uint8_t len; - switch(m_LastCMD) { - case CMD_ANTENNA_POWER: - case CMD_AUTHENTICATE: - case CMD_DEC_VALUE: - case CMD_INC_VALUE: - case CMD_WRITE_KEY: - case CMD_HALT_TAG: - case CMD_SLEEP: - len = 4; - break; - case CMD_WRITE4: - case CMD_WRITE_VALUE: - case CMD_READ_VALUE: - len = 8; - case CMD_SEEK_TAG: - case CMD_SELECT_TAG: - len = 11; - break; - default: - len = SIZE_PACKET; - } + // for uart, we need to add the sync bytes, 0xff, 0x00 + command.push_back(0xff); + command.push_back(0x00); + + // compute the length - command + data + uint8_t len = 1; // command + if (!data.empty()) + len += data.size(); - // If valid data received, process the response packet - if (i2cRecievePacket(len) > 0) { - // Init response variables - m_TagType = m_TagLength = *m_TagString = 0; + command.push_back(len); - // If packet length is 2, the command failed. Set error code. - errorCode = getPacketLength () < 3 ? m_Data[2] : 0; + cksum += len; - // Process command response - switch (getCommand ()) { - case CMD_RESET: - case CMD_VERSION: - // RESET and VERSION commands produce the firmware version - len = std::min ((unsigned int) getPacketLength(), (unsigned int) sizeof(m_Version)) - 1; - memcpy(m_Version, m_Data + 2, len); - m_Version[len] = 0; - break; + // now the command + command.push_back(cmd); + cksum += cmd; - case CMD_SEEK_TAG: - case CMD_SELECT_TAG: - // If no error, get tag number - if(errorCode == 0 && getPacketLength () >= 6) - { - m_TagLength = getPacketLength () - 2; - m_TagType = m_Data[2]; - memcpy(m_TagNumber, m_Data + 3, m_TagLength); - arrayToHex (m_TagString, m_TagNumber, m_TagLength); - } - break; - - case CMD_AUTHENTICATE: - break; - - case CMD_READ16: - break; - - case CMD_WRITE16: - case CMD_WRITE4: - break; - - case CMD_ANTENNA_POWER: - errorCode = 0; - antennaPower = m_Data[2]; - break; - - case CMD_SLEEP: - // If in SLEEP mode, no data is available - return false; - } - - // Data available - return true; - } - // No data available - return false; -} - -uint16_t -SM130::i2cRecievePacket (uint32_t len) { - int readByte = 0; - - mraa_i2c_address(m_i2Ctx, m_i2cAddr); - readByte = mraa_i2c_read(m_i2Ctx, m_Data, len); - - if (readByte > 0) { - // verify checksum if length > 0 and <= SIZE_PAYLOAD - if (m_Data[0] > 0 && m_Data[0] <= SIZE_PAYLOAD) + // now the data if any + if (!data.empty()) + { + for (int i=0; i> 4); - *s++ = toHex(array[i]); +#ifdef SM130_DEBUG + cerr << "CMD: " << string2HexString(command) << endl; +#endif // SM130_DEBUG + + // send it + m_uart.writeStr(command); + + // if the command is SET_BAUD, then switch to the new baudrate here + // before attempting to read the response (and hope it worked). + if (cmd == CMD_SET_BAUD) + { + usleep(100000); // 100ms + setBaudRate(m_baud); } - *s = 0; -} - -char -SM130::toHex (uint8_t b) { - b = b & 0x0f; - - return b < 10 ? b + '0' : b + 'A' - 10; -} - -mraa_result_t -SM130::i2cTransmitPacket (uint32_t len) { - mraa_result_t error = MRAA_SUCCESS; - uint8_t sum = 0; - - // Save last command - m_LastCMD = m_Data[0]; - - // calculate the sum check - for (int i = 0; i < len; i++) { - sum += m_Data[i]; + // now wait for a response + if (!m_uart.dataAvailable(defaultDelay)) + { + cerr << __FUNCTION__ << ": timeout waiting for response" << endl; + return ""; } - // placing the sum check to the last byte of the packet - m_Data[len + 1] = sum; + string resp = m_uart.readStr(maxLen); - error = mraa_i2c_address (m_i2Ctx, m_i2cAddr); - error = mraa_i2c_write (m_i2Ctx, m_Data, len + 1); +#ifdef SM130_DEBUG + cerr << "RSP: " << string2HexString(resp) << endl; +#endif // SM130_DEBUG - return error; + if (!((uint8_t)resp[0] == 0xff && (uint8_t)resp[1] == 0x00)) + { + cerr << __FUNCTION__ << ": invalid packet header" << endl; + return ""; + } + + // check size - 2 header bytes + len + cksum. + if (resp.size() != ((uint8_t)resp[2] + 2 + 1 + 1)) + { + cerr << __FUNCTION__ << ": invalid packet length, expected " + << int((uint8_t)resp[2] + 2 + 1 + 1) + << ", got " << resp.size() << endl; + return ""; + } + + // verify the cksum + cksum = 0; + for (int i=2; i<(resp.size() - 1); i++) + cksum += (uint8_t)resp[i]; + + if (cksum != (uint8_t)resp[resp.size() - 1]) + { + cerr << __FUNCTION__ << ": invalid checksum, expected " + << int(cksum) << ", got " << (uint8_t)resp[resp.size()-1] << endl; + return ""; + } + + // we could also verify that the command code returned was for the + // command submitted... + + // now, remove the 2 header bytes and the checksum, leave the length + // and command. + resp.erase(resp.size() - 1, 1); // cksum + resp.erase(0, 2); // header bytes + + // return the rest + return resp; } -mraa_result_t -SM130::sendCommand (uint8_t cmd) { - m_Data[0] = 1; - m_Data[1] = cmd; - i2cTransmitPacket(2); +string SM130::getFirmwareVersion() +{ + clearError(); + + string resp = sendCommand(CMD_VERSION, ""); + + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + return ""; + } + + // delete the len and cmd, return the rest + resp.erase(0, 2); + return resp; } +bool SM130::reset() +{ + clearError(); + + string resp = sendCommand(CMD_RESET, ""); + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + return false; + } + + return true; +} + +void SM130::hardwareReset() +{ + m_gpioReset.write(1); + usleep(100000); + m_gpioReset.write(0); +} + +bool SM130::select() +{ + clearError(); + + m_tagType = TAG_NONE; + m_uidLen = 0; + m_uid.clear(); + + string resp = sendCommand(CMD_SELECT_TAG, ""); + + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + return false; + } + + if ((uint8_t)resp[0] == 2) + { + // then we got an error of some sort, store the error code, str + // and bail. + m_lastErrorCode = resp[2]; + + switch (m_lastErrorCode) + { + case 'N': m_lastErrorString = "No tag present"; + break; + case 'U': m_lastErrorString = "Access failed, RF field is off"; + break; + default: m_lastErrorString = "Unknown error code"; + break; + } + return false; + } + + // if we are here, then store the uid info and tag type. + m_tagType = (TAG_TYPE_T)resp[2]; + + if ((uint8_t)resp[0] == 6) + m_uidLen = 4; // 4 byte uid + else + m_uidLen = 7; // 7 byte + + for (int i=0; i> 8) & 0xff); + data += ((value >> 16) & 0xff); + data += ((value >> 24) & 0xff); + + string resp = sendCommand(CMD_WRITE_VALUE, data); + + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + return false; + } + + if ((uint8_t)resp[0] == 2) + { + // then we got an error of some sort, store the error code, str + // and bail. + m_lastErrorCode = resp[2]; + + switch (m_lastErrorCode) + { + case 'F': m_lastErrorString = "Read failed during verification"; + break; + case 'N': m_lastErrorString = "No tag present"; + break; + case 'I': m_lastErrorString = "Invalid value block"; + break; + default: m_lastErrorString = "Unknown error code"; + break; + } + return false; + } + + return true; +} + +bool SM130::writeBlock4(uint8_t block, string contents) +{ + clearError(); + + // A little sanity checking... + if (contents.size() != 4) + { + throw std::invalid_argument(string(__FUNCTION__) + + ": You must supply 4 bytes for block content"); + + return false; + } + + string data; + data.push_back(block); + data += contents; + + string resp = sendCommand(CMD_WRITE4, data); + + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + return false; + } + + if ((uint8_t)resp[0] == 2) + { + // then we got an error of some sort, store the error code, str + // and bail. + m_lastErrorCode = resp[2]; + + switch (m_lastErrorCode) + { + case 'F': m_lastErrorString = "Write failed"; + break; + case 'N': m_lastErrorString = "No tag present"; + break; + case 'U': m_lastErrorString = "Read after write failed"; + break; + case 'X': m_lastErrorString = "Unable to read after write"; + break; + default: m_lastErrorString = "Unknown error code"; + break; + } + return false; + } + + return true; +} + +bool SM130::writeKey(uint8_t eepromSector, KEY_TYPES_T keyType, string key) +{ + clearError(); + + // A little sanity checking... + eepromSector &= 0x0f; // Only 0x00-0x0f is valid + + if (!(keyType == KEY_TYPE_A || keyType == KEY_TYPE_B)) + { + throw std::invalid_argument(string(__FUNCTION__) + + ": Key type must be A or B"); + + return false; + } + + if (key.size() != 6) + { + throw std::invalid_argument(string(__FUNCTION__) + + ": Key must be 6 bytes"); + + return false; + } + + string data; + data.push_back(eepromSector); + data += keyType; + data += key; + + string resp = sendCommand(CMD_WRITE_KEY, data); + + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + return false; + } + + // reponse len is always 2 + if ((uint8_t)resp[2] != 'L') + { + // then we got an error of some sort, store the error code, str + // and bail. + m_lastErrorCode = resp[2]; + + switch (m_lastErrorCode) + { + case 'N': m_lastErrorString = "Write master key failed"; + break; + default: m_lastErrorString = "Unknown error code"; + break; + } + return false; + } + + return true; +} + +int32_t SM130::adjustValueBlock(uint8_t block, int32_t value, bool incr) +{ + clearError(); + + string data; + data.push_back(block); + // put the value in, LSB first + data += (value & 0xff); + data += ((value >> 8) & 0xff); + data += ((value >> 16) & 0xff); + data += ((value >> 24) & 0xff); + + string resp = sendCommand(((incr) ? CMD_INC_VALUE : CMD_DEC_VALUE), data); + + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + return 0; + } + + if ((uint8_t)resp[0] == 2) + { + // then we got an error of some sort, store the error code, str + // and bail. + m_lastErrorCode = resp[2]; + + switch (m_lastErrorCode) + { + case 'F': m_lastErrorString = "Read failed during verification"; + break; + case 'N': m_lastErrorString = "No tag present"; + break; + case 'I': m_lastErrorString = "Invalid value block"; + break; + default: m_lastErrorString = "Unknown error code"; + break; + } + return 0; + } + + // now unpack the new value, LSB first + int32_t rv; + rv = ((uint8_t)resp[3] | + ((uint8_t)resp[4] << 8) | + ((uint8_t)resp[5] << 16) | + ((uint8_t)resp[6] << 24)); + + return rv; +} + +bool SM130::setAntennaPower(bool on) +{ + clearError(); + + string resp = sendCommand(CMD_ANTENNA_POWER, ""); + + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + return false; + } + + return true; +} + +uint8_t SM130::readPorts() +{ + clearError(); + + string resp = sendCommand(CMD_READ_PORT, ""); + + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + return 0; + } + + // only the first 2 bits are valid + return (resp[2] & 3); +} + +bool SM130::writePorts(uint8_t val) +{ + clearError(); + + // only the first 2 bits are valid + val &= 3; + + string data; + data.push_back(val); + + string resp = sendCommand(CMD_WRITE_PORT, data); + + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + return false; + } + + return true; +} + +bool SM130::haltTag() +{ + clearError(); + + string resp = sendCommand(CMD_HALT_TAG, ""); + + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + return false; + } + + // reponse len is always 2 + if ((uint8_t)resp[2] != 'L') + { + // then we got an error of some sort, store the error code, str + // and bail. + m_lastErrorCode = resp[2]; + + switch (m_lastErrorCode) + { + case 'U': m_lastErrorString = "Can not halt, RF field is off"; + break; + default: m_lastErrorString = "Unknown error code"; + break; + } + return false; + } + + return true; +} + +bool SM130::setSM130BaudRate(int baud) +{ + clearError(); + + uint8_t newBaud; + + switch (baud) + { + case 9600: newBaud = 0x00; + break; + case 19200: newBaud = 0x01; + break; + case 38400: newBaud = 0x02; + break; + case 57600: newBaud = 0x03; + break; + case 115200: newBaud = 0x04; + break; + default: + throw std::invalid_argument(string(__FUNCTION__) + + ": Invalid SM130 baud rate specified"); + } + + // WARNING: this is a dangerous command + int oldBaud = m_baud; + m_baud = baud; + + string data; + data.push_back(newBaud); + + string resp = sendCommand(CMD_SET_BAUD, data); + + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + cerr << __FUNCTION__ << ": restoring old baud rate" << endl; + + setBaudRate(oldBaud); + return false; + } + + // otherwise assume success, possibly incorrectly + return true; +} + +bool SM130::sleep() +{ + clearError(); + + string resp = sendCommand(CMD_SLEEP, ""); + + if (resp.empty()) + { + cerr << __FUNCTION__ << ": failed" << endl; + return false; + } + + return true; +} + +string SM130::string2HexString(string input) +{ + static const char* const lut = "0123456789abcdef"; + size_t len = input.size(); + + string output; + output.reserve(3 * len); + for (size_t i = 0; i < len; ++i) + { + const unsigned char c = input[i]; + output.push_back(lut[c >> 4]); + output.push_back(lut[c & 15]); + output.push_back(' '); + } + + return output; +} + +void SM130::initClock() +{ + gettimeofday(&m_startTime, NULL); +} + +uint32_t SM130::getMillis() +{ + struct timeval elapsed, now; + uint32_t elapse; + + // get current time + gettimeofday(&now, NULL); + + // compute the delta since m_startTime + if( (elapsed.tv_usec = now.tv_usec - m_startTime.tv_usec) < 0 ) + { + elapsed.tv_usec += 1000000; + elapsed.tv_sec = now.tv_sec - m_startTime.tv_sec - 1; + } + else + { + elapsed.tv_sec = now.tv_sec - m_startTime.tv_sec; + } + + elapse = (uint32_t)((elapsed.tv_sec * 1000) + (elapsed.tv_usec / 1000)); + + // never return 0 + if (elapse == 0) + elapse = 1; + + return elapse; +} + +bool SM130::waitForTag(uint32_t timeout) +{ + initClock(); + + do + { + if (select()) + { + // success + return true; + } + else + { + // If there was an error, fail if it's anything other than a + // tag not present + if (getLastErrorCode() != 'N') + return false; + + // otherwise, sleep for 100ms and try again + usleep(100000); + } + } while (getMillis() <= timeout); + + return false; +} + +string SM130::tag2String(TAG_TYPE_T tag) +{ + switch (tag) + { + case TAG_MIFARE_ULTRALIGHT: return "MiFare Ultralight"; + case TAG_MIFARE_1K: return "MiFare 1K"; + case TAG_MIFARE_4K: return "MiFare 4K"; + case TAG_UNKNOWN: return "Unknown Tag Type"; + default: return "Invalid Tag Type"; + } +} + + + diff --git a/src/sm130/sm130.h b/src/sm130/sm130.h index dc04f4fb..544cae28 100644 --- a/src/sm130/sm130.h +++ b/src/sm130/sm130.h @@ -1,8 +1,6 @@ /* - * Author: Yevgeniy Kiveisha - * Copyright (c) 2014 Intel Corporation. - * - * Based on SM130 library developed by Marc Boon + * Author: Jon Trulson + * Copyright (c) 2015 Intel Corporation. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -26,142 +24,443 @@ #pragma once #include -#include -#include -#include -#include +#include + #include +#include +#include +#include +#include +#include +#include +#include -#define SIZE_PAYLOAD 18 // maximum payload size of I2C packet -#define SIZE_PACKET (SIZE_PAYLOAD + 2) // total I2C packet size, including length uint8_t and checksum +#include +#include -#define HIGH 1 -#define LOW 0 +#define SM130_DEFAULT_UART 0 +#define SM130_DEFAULT_RESET_PIN 13 namespace upm { + + /** + * @brief SM130 RFID Reader Module library + * @defgroup sm130 libupm-sm130 + * @ingroup sparkfun uart gpio rfid + */ -/** - * @brief SM130 RFID Reader Module library - * @defgroup sm130 libupm-sm130 - * @ingroup sparkfun gpio rfid - */ -/** - * @library sm130 - * @sensor sm130 - * @comname SM130 RFID Reader - * @type rfid - * @man sparkfun - * @web https://www.sparkfun.com/products/10126 - * @con gpio - * - * @brief API for the SM130 RFID Reader Module - * - * This file defines the SM130 interface for the sm130 RFID library - * - * @image html sm130.jpg - *
SM130 RFID Reader image provided by SparkFun* under - * - * CC BY-NC-SA-3.0. - * - * @snippet sm130.cxx Interesting - */ -class SM130 { + /** + * @library sm130 + * @sensor sm130 + * @comname SM130 RFID Reader + * @type rfid + * @man sparkfun + * @web https://www.sparkfun.com/products/10126 + * @con uart gpio + * + * @brief API for the SM130 RFID Reader Module + * + * This file defines the SM130 interface for the sm130 RFID library + * + * This module was developed using an SM130 and a Sparkfun RFID + * Evaluation shield using a UART for communications. It should be + * fairly trivial to add support for I2C communication in the + * future, if you have the correct firmware on the SM130. + * + * @image html sm130.jpg + *
SM130 RFID Reader image provided by SparkFun* under + * + * CC BY-NC-SA-3.0. + * + * @snippet sm130.cxx Interesting + */ - uint8_t m_Data[SIZE_PACKET]; //!< packet data - char m_Version[8]; //!< version string - uint8_t m_TagNumber[7]; //!< tag number as uint8_t array - uint8_t m_TagLength; //!< length of tag number in uint8_ts (4 or 7) - char m_TagString[15]; //!< tag number as hex string - uint8_t m_TagType; //!< type of tag - char errorCode; //!< error code from some commands - uint8_t antennaPower; //!< antenna power level - uint8_t m_LastCMD; //!< last sent command + class SM130 { + + public: - public: - static const uint8_t MIFARE_ULTRALIGHT = 1; - static const uint8_t MIFARE_1K = 2; - static const uint8_t MIFARE_4K = 3; + // Valid commands + typedef enum { + CMD_RESET = 0x80, + CMD_VERSION = 0x81, + CMD_SEEK_TAG = 0x82, + CMD_SELECT_TAG = 0x83, + CMD_AUTHENTICATE = 0x85, + CMD_READ16 = 0x86, + CMD_READ_VALUE = 0x87, + CMD_WRITE16 = 0x89, + CMD_WRITE_VALUE = 0x8a, + CMD_WRITE4 = 0x8b, + CMD_WRITE_KEY = 0x8c, + CMD_INC_VALUE = 0x8d, + CMD_DEC_VALUE = 0x8e, + CMD_ANTENNA_POWER = 0x90, + CMD_READ_PORT = 0x91, + CMD_WRITE_PORT = 0x92, + CMD_HALT_TAG = 0x93, + CMD_SET_BAUD = 0x94, + CMD_SLEEP = 0x96 + } CMD_T; - static const uint8_t CMD_RESET = 0x80; - static const uint8_t CMD_VERSION = 0x81; - static const uint8_t CMD_SEEK_TAG = 0x82; - static const uint8_t CMD_SELECT_TAG = 0x83; - static const uint8_t CMD_AUTHENTICATE = 0x85; - static const uint8_t CMD_READ16 = 0x86; - static const uint8_t CMD_READ_VALUE = 0x87; - static const uint8_t CMD_WRITE16 = 0x89; - static const uint8_t CMD_WRITE_VALUE = 0x8a; - static const uint8_t CMD_WRITE4 = 0x8b; - static const uint8_t CMD_WRITE_KEY = 0x8c; - static const uint8_t CMD_INC_VALUE = 0x8d; - static const uint8_t CMD_DEC_VALUE = 0x8e; - static const uint8_t CMD_ANTENNA_POWER = 0x90; - static const uint8_t CMD_READ_PORT = 0x91; - static const uint8_t CMD_WRITE_PORT = 0x92; - static const uint8_t CMD_HALT_TAG = 0x93; - static const uint8_t CMD_SET_BAUD = 0x94; - static const uint8_t CMD_SLEEP = 0x96; + // valid tag types. + typedef enum { + TAG_NONE = 0x00, // error/invalid - /** - * Instantiates an SM130 object - * - * @param di Data pin - * @param dcki Clock pin - */ - SM130 (int bus, int devAddr, int rst, int dready); + TAG_MIFARE_ULTRALIGHT = 0x01, + TAG_MIFARE_1K = 0x02, + TAG_MIFARE_4K = 0x03, + TAG_UNKNOWN = 0xff + } TAG_TYPE_T; - /** - * SM130 object destructor - */ - ~SM130 (); + // Valid authentication keys + typedef enum { + KEY_TYPE_EEPROM_A0 = 0x10, + KEY_TYPE_EEPROM_A1 = 0x11, + KEY_TYPE_EEPROM_A2 = 0x12, + KEY_TYPE_EEPROM_A3 = 0x13, + KEY_TYPE_EEPROM_A4 = 0x14, + KEY_TYPE_EEPROM_A5 = 0x15, + KEY_TYPE_EEPROM_A6 = 0x16, + KEY_TYPE_EEPROM_A7 = 0x17, + KEY_TYPE_EEPROM_A8 = 0x18, + KEY_TYPE_EEPROM_A9 = 0x19, + KEY_TYPE_EEPROM_A10 = 0x1a, + KEY_TYPE_EEPROM_A11 = 0x1b, + KEY_TYPE_EEPROM_A12 = 0x1c, + KEY_TYPE_EEPROM_A13 = 0x1d, + KEY_TYPE_EEPROM_A14 = 0x1e, + KEY_TYPE_EEPROM_A15 = 0x1f, - /** - * Gets the firmware version string. - */ - const char* getFirmwareVersion (); + KEY_TYPE_EEPROM_B0 = 0x20, + KEY_TYPE_EEPROM_B1 = 0x21, + KEY_TYPE_EEPROM_B2 = 0x22, + KEY_TYPE_EEPROM_B3 = 0x23, + KEY_TYPE_EEPROM_B4 = 0x24, + KEY_TYPE_EEPROM_B5 = 0x25, + KEY_TYPE_EEPROM_B6 = 0x26, + KEY_TYPE_EEPROM_B7 = 0x27, + KEY_TYPE_EEPROM_B8 = 0x28, + KEY_TYPE_EEPROM_B9 = 0x29, + KEY_TYPE_EEPROM_B10 = 0x2a, + KEY_TYPE_EEPROM_B11 = 0x2b, + KEY_TYPE_EEPROM_B12 = 0x2c, + KEY_TYPE_EEPROM_B13 = 0x2d, + KEY_TYPE_EEPROM_B14 = 0x2e, + KEY_TYPE_EEPROM_B15 = 0x2f, - /** - * Checks for availability of a valid response packet. - * - * This function should always be called and return true prior to using the results - * of a command. - * - * @returns True if a valid response packet is available - */ - uint8_t available (); + KEY_TYPE_A = 0xaa, + KEY_TYPE_B = 0xbb, - /** - * Returns the packet length, excluding the checksum - */ - uint8_t getPacketLength () { return this->m_Data[0]; }; + KEY_TYPE_A_AND_TRANSPORT_F = 0xff + } KEY_TYPES_T; - /** - * Returns the last executed command - */ - uint8_t getCommand () { return this->m_Data[1]; }; + /** + * Instantiates an SM130 object + * + * @param uart The UART port. Default is 0. + * @param reset The Reset pin. Default is 13. + */ + SM130 (int uart=SM130_DEFAULT_UART, int reset=SM130_DEFAULT_RESET_PIN); - /** - * Returns the name of the component - */ - std::string name() - { - return m_name; - } - private: - std::string m_name; - mraa_gpio_context m_resetPinCtx; - mraa_gpio_context m_dataReadyPinCtx; + /** + * SM130 object destructor + */ + ~SM130 (); - int m_i2cAddr; - int m_bus; - mraa_i2c_context m_i2Ctx; + /** + * Sets the baud rate for the device. The default is 19200. + * + * @param baud Desired baud rate, default 19200 + * @return mraa_result_t value + */ + mraa_result_t setBaudRate(int baud=19200); - void arrayToHex (char *s, uint8_t array[], uint8_t len); - char toHex (uint8_t b); + /** + * Gets the firmware version string. + * + * @return The firmware revision + */ + std::string getFirmwareVersion(); - uint16_t i2cRecievePacket (uint32_t len); - mraa_result_t i2cTransmitPacket (uint32_t len); - mraa_result_t sendCommand (uint8_t cmd); -}; + /** + * Issues a reset command to the device. + * + * @return true if successful + */ + bool reset(); + + /** + * Resets the device using the hardware RESET pin. This is + * required if the device has been put to sleep using the sleep() + * method. + */ + void hardwareReset(); + + /** + * Checks to see if a tag is in the RF field, and selects it if + * one is present. + * + * @return true if a tag was detected, false if no tag is present + * or an error was detected. + */ + bool select(); + + /** + * Waits for a tag to enter the RF field for up to 'timeout' + * milliseconds. It will call select() every 100ms until 'timeout' + * has been exceeded. + * + * @param timeout The number of milliseconds to wait for a tag to appear + * @return true if a tag was detected, false if no tag was + * detected within the timeout value, or an error occurred + */ + bool waitForTag(uint32_t timeout); + + /** + * Set the authentication key for a block. Depending on the + * permissions on the tag, the correct key must be authenticated + * for that block in order to perform read and write operations. + * + * @param block The block to authenticate for + * @param keyType one of the KEY_TYPE_T values + * @param key The 6 byte key to use for Type A and Type B keys + * @return true if authentication was successful, false otherwise + */ + bool authenticate(uint8_t block, KEY_TYPES_T keyType, std::string key=""); + + /** + * Read a 16 byte block. Depending on the tag, authentication of + * the block may be required for this method to succeed. + * + * @param block The block to read + * @return The 16 byte block if successful, an empty string otherwise + */ + std::string readBlock16(uint8_t block); + + /** + * Read a 4 byte value block. Depending on the tag, authentication of + * the block may be required for this method to succeed. + * + * @param block The block to read + * @return The 4 byte signed integer value block if successful, 0 otherwise + */ + int32_t readValueBlock(uint8_t block); + + /** + * Write 16 bytes to a block. Depending on the tag, authentication of + * the block may be required for this method to succeed. + * + * @param block The block to write + * @param contents A 16 byte string containing the data to write + * @return true if successful, false otherwise + */ + bool writeBlock16(uint8_t block, std::string contents); + + /** + * Write to a 4 byte value block. Depending on the tag, + * authentication of the block may be required for this method to + * succeed. + * + * @param block The block to write + * @param value the signed 4 byte integer to write to the value block + * @return true if successful, false otherwise + */ + bool writeValueBlock(uint8_t block, int32_t value); + + /** + * Write 4 bytes to a block. This is typically used for + * Ultralight tags. Depending on the tag, authentication of the + * block may be required for this method to succeed. + * + * @param block The block to write + * @param contents A 4 byte string containing the data to write + * @return true if successful, false otherwise + */ + bool writeBlock4(uint8_t block, std::string contents); + + /** + * Write a key into one of the 16 EEPROM key slots. This can be a + * Type A or Type B key. It is not possible to read these keys + * once written. Once stored, the key can be used for + * authentication without having to send the key itself. You can + * then use the appropriate KEY_TYPE_EEPROM_* keyTypes in a call + * to authenticate(). + * + * @param eepromSector A number between 0 and 15, indicating the + * EEPROM sector you want to store the key in + * @param keyType Either KEY_TYPE_A or KEY_TYPE_B + * @param key The 6 byte key to store in the EEPROM + * @return true if successful, false otherwise + */ + bool writeKey(uint8_t eepromSector, KEY_TYPES_T keyType, std::string key); + + /** + * Increment or decrement a value block. + * + * @param block The block to adjust + * @param value The number to increment or decrement the value block by + * @param incr true to increment, false to decrement + * @return The contents of the value block after the operation has + * completed. + */ + int32_t adjustValueBlock(uint8_t block, int32_t value, bool incr); + + /** + * Turn the antenna power on or off. The power is on by default + * after a reset. If you turn off the antenna, and methods used + * for interacting with tags will fail until power is re-enabled. + * + * @param on true to enable antenna power, false to disable + * @return true if successful, false otherwise + */ + bool setAntennaPower(bool on); + + /** + * Read the status of the 2 onboard GPIO input pins. Bit 0 is for + * input 0, bit 1 for input 1. All other bits will be 0. + * + * @return bitmask of input port status values + */ + uint8_t readPorts(); + + /** + * Set the output status of the 2 onboard gpio outputs. Bit 0 is for + * output 0, bit 1 for output 1. All other bits will be discarded. + * + * @param val bitmask of output status bits to write + * @return true if successful, false otherwise + */ + bool writePorts(uint8_t val); + + /** + * Halts a tag. Once a tag is halted, it cannot be accessed until + * it is removed and reinserted into the RF field and selected. + * + * @return true if successful, false otherwise + */ + bool haltTag(); + + /** + * Changes the baud rate of the SM130. WARNING: This is a + * potentially dangerous command that could cause you to lose + * contact with the device. Once the command is validated and + * issued, the host baudrate will be changed to match, and this + * method will wait for a response at the new baudrate for up to 1 + * second. + * + * If this response does not arrive, the old baudrate will be + * restored, though there is no way to know whether the SM130 + * actually succeessfully executed the baudrate change. + * + * Once the SM130 has changed it's baudrate, the new value will be + * stored in it's EEPROM, and any further access to the device + * will need to use the new baudrate. This is true even after a + * power on reset. + * + * @param baud The new baud rate to set. Valid values are 9600, + * 19200, 38400, 57600, and 115200. + * @return true if successful, false otherwise + */ + bool setSM130BaudRate(int baud); + + /** + * Put the SM130 to sleep. Once the device has been put to sleep, + * the only way to wake it is via hardwareReset() or a power + * cycle. + * + * @return true if successful, false otherwise + */ + bool sleep(); + + /** + * Get the last error that occurred. After a successful + * operation, this will be 0. See the datasheet for the various + * errors that can occur in various circumstances. + * + * @return The last error code, or 0 if the last operation succeeded. + */ + char getLastErrorCode() { return m_lastErrorCode; }; + + /** + * Get the text representation of the last error that occurred. + * The returned string is empty if the last operation completed + * successfully. + * + * @return The last error string if an error occurred, or an empty + * string if the last operation succeeded. + */ + std::string getLastErrorString() { return m_lastErrorString; }; + + /** + * Get the UID length of the currently selected tag. + * + * @return The UID length of the currently selected tag, or 0 if + * no tag is currently selected. + */ + int getUIDLen() { return m_uidLen; }; + + /** + * Get the UID of the currently selected tag. + * + * @return The UID of the currently selected tag, or an empty string if + * no tag is currently selected. + */ + std::string getUID() { return m_uid; }; + + /** + * Get the tag type of the currently selected tag. + * + * @return The tag type of the currently selected tag, or TAG_NONE + * if no tag is currently selected. + */ + TAG_TYPE_T getTagType() { return m_tagType; }; + + /** + * Convert the supplied tag type into a human readable string. + * + * @param tag One of the TAG_TYPE_T values + * @return A string representation of the supplied tag type + */ + std::string tag2String(TAG_TYPE_T tag); + + /** + * This is a convenience function that converts a supplied string + * into a space separated hex formatted string. This can be + * useful for printing out binary data in a human readable format, + * like the UID. + * + * @param input The string to convert + * @return A string representation of the input in space separated + * hex values + */ + std::string string2HexString(std::string input); + + protected: + mraa::Uart m_uart; + mraa::Gpio m_gpioReset; + + std::string sendCommand(CMD_T cmd, std::string data); + void initClock(); + uint32_t getMillis(); + + private: + int m_uidLen; + std::string m_uid; + + char m_lastErrorCode; + std::string m_lastErrorString; + + TAG_TYPE_T m_tagType; + + int m_baud; + + struct timeval m_startTime; + + void clearError() + { + m_lastErrorCode = 0; + m_lastErrorString.clear(); + } + }; }