diff options
Diffstat (limited to 'src/nfc/qnearfieldtagtype1.cpp')
-rw-r--r-- | src/nfc/qnearfieldtagtype1.cpp | 735 |
1 files changed, 735 insertions, 0 deletions
diff --git a/src/nfc/qnearfieldtagtype1.cpp b/src/nfc/qnearfieldtagtype1.cpp new file mode 100644 index 00000000..eb1d6c7e --- /dev/null +++ b/src/nfc/qnearfieldtagtype1.cpp @@ -0,0 +1,735 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnearfieldtagtype1.h" +#include "qnearfieldtarget_p.h" +#include "qndefmessage.h" +#include "qtlv_p.h" + +#include <QtCore/QByteArray> +#include <QtCore/QVariant> + +#include <QtCore/QDebug> + +/*! + \class QNearFieldTagType1 + \brief The QNearFieldTagType1 class provides an interface for communicating with an NFC Tag + Type 1 tag. + \since 5.0 + + \ingroup connectivity-nfc + \inmodule QtConnectivity +*/ + +/*! + \enum QNearFieldTagType1::WriteMode + \brief This enum describes the write modes that are supported. + + \value EraseAndWrite The memory is erased before the new value is written. + \value WriteOnly The memory is not erased before the new value is written. The effect of + this mode is that the final value store is the bitwise or of the data + to be written and the original data value. +*/ + +/*! + \fn Type QNearFieldTagType1::type() const + \reimp +*/ + +class QNearFieldTagType1Private +{ + Q_DECLARE_PUBLIC(QNearFieldTagType1) + +public: + QNearFieldTagType1Private(QNearFieldTagType1 *q) + : q_ptr(q), m_readNdefMessageState(NotReadingNdefMessage), + m_tlvReader(0), + m_writeNdefMessageState(NotWritingNdefMessage) + { } + + QNearFieldTagType1 *q_ptr; + + QMap<QNearFieldTarget::RequestId, QByteArray> m_pendingInternalCommands; + + enum ReadNdefMessageState { + NotReadingNdefMessage, + NdefReadCheckingIdentification, + NdefReadCheckingNdefMagicNumber, + NdefReadReadingTlv + }; + + void progressToNextNdefReadMessageState(); + ReadNdefMessageState m_readNdefMessageState; + QNearFieldTarget::RequestId m_readNdefRequestId; + + QTlvReader *m_tlvReader; + QNearFieldTarget::RequestId m_nextExpectedRequestId; + + enum WriteNdefMessageState { + NotWritingNdefMessage, + NdefWriteCheckingIdentification, + NdefWriteCheckingNdefMagicNumber, + NdefWriteReadingTlv, + NdefWriteWritingTlv, + NdefWriteWritingTlvFlush + }; + + void progressToNextNdefWriteMessageState(); + WriteNdefMessageState m_writeNdefMessageState; + QNearFieldTarget::RequestId m_writeNdefRequestId; + QList<QNdefMessage> m_ndefWriteMessages; + + QTlvWriter *m_tlvWriter; + + typedef QPair<quint8, QByteArray> Tlv; + QList<Tlv> m_tlvs; +}; + +void QNearFieldTagType1Private::progressToNextNdefReadMessageState() +{ + Q_Q(QNearFieldTagType1); + + switch (m_readNdefMessageState) { + case NotReadingNdefMessage: + m_readNdefMessageState = NdefReadCheckingIdentification; + m_nextExpectedRequestId = q->readIdentification(); + break; + case NdefReadCheckingIdentification: { + const QByteArray data = q->requestResponse(m_nextExpectedRequestId).toByteArray(); + + if (data.isEmpty()) { + m_readNdefMessageState = NotReadingNdefMessage; + m_nextExpectedRequestId = QNearFieldTarget::RequestId(); + emit q->error(QNearFieldTarget::NdefReadError, m_readNdefRequestId); + m_readNdefRequestId = QNearFieldTarget::RequestId(); + break; + } + + quint8 hr0 = data.at(0); + + // Check if target is a NFC TagType1 tag + if (!(hr0 & 0x10)) { + m_readNdefMessageState = NotReadingNdefMessage; + m_nextExpectedRequestId = QNearFieldTarget::RequestId(); + emit q->error(QNearFieldTarget::NdefReadError, m_readNdefRequestId); + m_readNdefRequestId = QNearFieldTarget::RequestId(); + break; + } + + m_readNdefMessageState = NdefReadCheckingNdefMagicNumber; + m_nextExpectedRequestId = q->readByte(8); + break; + } + case NdefReadCheckingNdefMagicNumber: { + quint8 ndefMagicNumber = q->requestResponse(m_nextExpectedRequestId).toUInt(); + m_nextExpectedRequestId = QNearFieldTarget::RequestId(); + + if (ndefMagicNumber != 0xe1) { + m_readNdefMessageState = NotReadingNdefMessage; + emit q->error(QNearFieldTarget::NdefReadError, m_readNdefRequestId); + m_readNdefRequestId = QNearFieldTarget::RequestId(); + break; + } + + m_readNdefMessageState = NdefReadReadingTlv; + m_tlvReader = new QTlvReader(q); + + // fall through + } + case NdefReadReadingTlv: + while (!m_tlvReader->atEnd()) { + if (!m_tlvReader->readNext()) + break; + + // NDEF Message TLV + if (m_tlvReader->tag() == 0x03) { + Q_Q(QNearFieldTagType1); + + emit q->ndefMessageRead(QNdefMessage::fromByteArray(m_tlvReader->data())); + } + } + + m_nextExpectedRequestId = m_tlvReader->requestId(); + if (!m_nextExpectedRequestId.isValid()) { + delete m_tlvReader; + m_tlvReader = 0; + m_readNdefMessageState = NotReadingNdefMessage; + emit q->requestCompleted(m_readNdefRequestId); + m_readNdefRequestId = QNearFieldTarget::RequestId(); + } + break; + } +} + +void QNearFieldTagType1Private::progressToNextNdefWriteMessageState() +{ + Q_Q(QNearFieldTagType1); + + switch (m_writeNdefMessageState) { + case NotWritingNdefMessage: + m_writeNdefMessageState = NdefWriteCheckingIdentification; + m_nextExpectedRequestId = q->readIdentification(); + break; + case NdefWriteCheckingIdentification: { + const QByteArray data = q->requestResponse(m_nextExpectedRequestId).toByteArray(); + + if (data.isEmpty()) { + m_writeNdefMessageState = NotWritingNdefMessage; + m_nextExpectedRequestId = QNearFieldTarget::RequestId(); + emit q->error(QNearFieldTarget::NdefWriteError, m_writeNdefRequestId); + m_writeNdefRequestId = QNearFieldTarget::RequestId(); + break; + } + + quint8 hr0 = data.at(0); + + // Check if target is a NFC TagType1 tag + if (!(hr0 & 0x10)) { + m_writeNdefMessageState = NotWritingNdefMessage; + m_nextExpectedRequestId = QNearFieldTarget::RequestId(); + emit q->error(QNearFieldTarget::NdefWriteError, m_writeNdefRequestId); + m_writeNdefRequestId = QNearFieldTarget::RequestId(); + break; + } + + m_writeNdefMessageState = NdefWriteCheckingNdefMagicNumber; + m_nextExpectedRequestId = q->readByte(8); + break; + } + case NdefWriteCheckingNdefMagicNumber: { + quint8 ndefMagicNumber = q->requestResponse(m_nextExpectedRequestId).toUInt(); + m_nextExpectedRequestId = QNearFieldTarget::RequestId(); + + if (ndefMagicNumber != 0xe1) { + m_writeNdefMessageState = NotWritingNdefMessage; + emit q->error(QNearFieldTarget::NdefWriteError, m_writeNdefRequestId); + m_writeNdefRequestId = QNearFieldTarget::RequestId(); + break; + } + + m_writeNdefMessageState = NdefWriteReadingTlv; + m_tlvReader = new QTlvReader(q); + + // fall through + } + case NdefWriteReadingTlv: + while (!m_tlvReader->atEnd()) { + if (!m_tlvReader->readNext()) + break; + + quint8 tag = m_tlvReader->tag(); + if (tag == 0x01 || tag == 0x02 || tag == 0xfd) + m_tlvs.append(qMakePair(tag, m_tlvReader->data())); + } + + m_nextExpectedRequestId = m_tlvReader->requestId(); + if (m_nextExpectedRequestId.isValid()) + break; + + delete m_tlvReader; + m_tlvReader = 0; + m_writeNdefMessageState = NdefWriteWritingTlv; + + // fall through + case NdefWriteWritingTlv: + m_tlvWriter = new QTlvWriter(q); + + // write old TLVs + foreach (const Tlv &tlv, m_tlvs) + m_tlvWriter->writeTlv(tlv.first, tlv.second); + + // write new NDEF message TLVs + foreach (const QNdefMessage &message, m_ndefWriteMessages) + m_tlvWriter->writeTlv(0x03, message.toByteArray()); + + // write terminator TLV + m_tlvWriter->writeTlv(0xfe); + + m_writeNdefMessageState = NdefWriteWritingTlvFlush; + + // fall through + case NdefWriteWritingTlvFlush: + // flush the writer + if (m_tlvWriter->process(true)) { + m_nextExpectedRequestId = QNearFieldTarget::RequestId(); + m_writeNdefMessageState = NotWritingNdefMessage; + delete m_tlvWriter; + m_tlvWriter = 0; + emit q->ndefMessagesWritten(); + emit q->requestCompleted(m_writeNdefRequestId); + m_writeNdefRequestId = QNearFieldTarget::RequestId(); + } else { + m_nextExpectedRequestId = m_tlvWriter->requestId(); + if (!m_nextExpectedRequestId.isValid()) { + m_writeNdefMessageState = NotWritingNdefMessage; + delete m_tlvWriter; + m_tlvWriter = 0; + emit q->error(QNearFieldTarget::NdefWriteError, m_writeNdefRequestId); + m_writeNdefRequestId = QNearFieldTarget::RequestId(); + } + } + break; + } +} + +static QVariant decodeResponse(const QByteArray &command, const QByteArray &response) +{ + switch (command.at(0)) { + case 0x01: // READ + if (command.at(1) == response.at(0)) + return quint8(response.at(1)); + break; + case 0x53: { // WRITE-E + quint8 address = command.at(1); + quint8 data = command.at(2); + quint8 writeAddress = response.at(0); + quint8 writeData = response.at(1); + + return ((writeAddress == address) && (writeData == data)); + } + case 0x1a: { // WRITE-NE + quint8 address = command.at(1); + quint8 data = command.at(2); + quint8 writeAddress = response.at(0); + quint8 writeData = response.at(1); + + return ((writeAddress == address) && ((writeData & data) == data)); + } + case 0x10: { // RSEG + quint8 segmentAddress = quint8(command.at(1)) >> 4; + quint8 readSegmentAddress = quint8(response.at(0)) >> 4; + if (readSegmentAddress == segmentAddress) + return response.mid(1); + break; + } + case 0x02: { // READ8 + quint8 blockAddress = command.at(1); + quint8 readBlockAddress = response.at(0); + if (readBlockAddress == blockAddress) + return response.mid(1); + break; + } + case 0x54: { // WRITE-E8 + quint8 blockAddress = command.at(1); + QByteArray data = command.mid(2, 8); + quint8 writeBlockAddress = response.at(0); + QByteArray writeData = response.mid(1); + + return ((writeBlockAddress == blockAddress) && (writeData == data)); + } + case 0x1b: { // WRITE-NE8 + quint8 blockAddress = command.at(1); + QByteArray data = command.mid(2, 8); + quint8 writeBlockAddress = response.at(0); + QByteArray writeData = response.mid(1); + + if (writeBlockAddress != blockAddress) + return false; + + for (int i = 0; i < writeData.length(); ++i) { + if ((writeData.at(i) & data.at(i)) != data.at(i)) + return false; + } + + return true; + } + } + + return QVariant(); +} + +/*! + Constructs a new tag type 1 near field target with \a parent. +*/ +QNearFieldTagType1::QNearFieldTagType1(QObject *parent) +: QNearFieldTarget(parent), d_ptr(new QNearFieldTagType1Private(this)) +{ +} + +/*! + Destroys the tag type 1 near field target. +*/ +QNearFieldTagType1::~QNearFieldTagType1() +{ + delete d_ptr; +} + +/*! + \reimp +*/ +bool QNearFieldTagType1::hasNdefMessage() +{ + RequestId id = readAll(); + if (!waitForRequestCompleted(id)) + return false; + + const QByteArray data = requestResponse(id).toByteArray(); + + if (data.isEmpty()) + return false; + + quint8 hr0 = data.at(0); + + // Check if target is a NFC TagType1 tag + if (!(hr0 & 0x10)) + return false; + + // Check if NDEF Message Magic number is present + quint8 nmn = data.at(10); + if (nmn != 0xe1) + return false; + + // Check if TLV contains NDEF Message + return true; +} + +/*! + \reimp +*/ +QNearFieldTarget::RequestId QNearFieldTagType1::readNdefMessages() +{ + Q_D(QNearFieldTagType1); + + d->m_readNdefRequestId = RequestId(new RequestIdPrivate); + + if (d->m_readNdefMessageState == QNearFieldTagType1Private::NotReadingNdefMessage) { + d->progressToNextNdefReadMessageState(); + } else { + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QNearFieldTarget::Error, NdefReadError), + Q_ARG(QNearFieldTarget::RequestId, d->m_readNdefRequestId)); + } + + return d->m_readNdefRequestId; +} + +/*! + \reimp +*/ +QNearFieldTarget::RequestId QNearFieldTagType1::writeNdefMessages(const QList<QNdefMessage> &messages) +{ + Q_D(QNearFieldTagType1); + + d->m_writeNdefRequestId = RequestId(new RequestIdPrivate); + + if (d->m_readNdefMessageState == QNearFieldTagType1Private::NotReadingNdefMessage && + d->m_writeNdefMessageState == QNearFieldTagType1Private::NotWritingNdefMessage) { + d->m_ndefWriteMessages = messages; + d->progressToNextNdefWriteMessageState(); + } else { + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QNearFieldTarget::Error, NdefWriteError), + Q_ARG(QNearFieldTarget::RequestId, d->m_readNdefRequestId)); + } + + return d->m_writeNdefRequestId; +} + +/*! + Returns the NFC Tag Type 1 specification version number that the tag supports. +*/ +quint8 QNearFieldTagType1::version() +{ + RequestId id = readByte(9); + if (!waitForRequestCompleted(id)) + return 0; + + quint8 versionNumber = requestResponse(id).toUInt(); + return versionNumber; +} + +/*! + Returns the memory size in bytes of the tag. +*/ +int QNearFieldTagType1::memorySize() +{ + RequestId id = readByte(10); + if (!waitForRequestCompleted(id)) + return 0; + + quint8 tms = requestResponse(id).toUInt(); + + return 8 * (tms + 1); +} + +/*! + Requests the identification bytes from the target. Returns a request id which can be used to + track the completion status of the request. + + Once the request completes successfully the response can be retrieved from the + requestResponse() function. The response of this request will be a QByteArray containing: HR0, + HR1, UID0, UID1, UID2 and UID3 in order. + + \sa requestCompleted(), waitForRequestCompleted() +*/ +QNearFieldTarget::RequestId QNearFieldTagType1::readIdentification() +{ + QByteArray command; + command.append(char(0x78)); // RID + command.append(char(0x00)); // Address (unused) + command.append(char(0x00)); // Data (unused) + command.append(uid().left(4)); // 4 bytes of UID + + return sendCommand(command); +} + +/*! + Requests all data in the static memory area of the target. Returns a request id which can be + used to track the completion status of the request. + + Once the request completes successfully the response can be retrieved from the + requestResponse() function. The response of this request will be a QByteArray containing: HR0 + and HR1 followed by the 120 bytes of data stored in the static memory area of the target. + + \sa requestCompleted(), waitForRequestCompleted() +*/ +QNearFieldTarget::RequestId QNearFieldTagType1::readAll() +{ + QByteArray command; + command.append(char(0x00)); // RALL + command.append(char(0x00)); // Address (unused) + command.append(char(0x00)); // Data (unused) + command.append(uid().left(4));// 4 bytes of UID + + return sendCommand(command); +} + +/*! + Requests a single byte from the static memory area of the tag. The \a address parameter + specifices the linear byte address to read. Returns a request id which can be used to track + the completion status of the request. + + Once the request completes successfully the response can be retrieved from the + requestResponse() function. The response of this request will be a quint8. + + \sa requestCompleted(), waitForRequestCompleted() +*/ +QNearFieldTarget::RequestId QNearFieldTagType1::readByte(quint8 address) +{ + if (address & 0x80) + return RequestId(); + + QByteArray command; + command.append(char(0x01)); // READ + command.append(char(address)); // Address + command.append(char(0x00)); // Data (unused) + command.append(uid().left(4)); // 4 bytes of UID + + RequestId id = sendCommand(command); + + Q_D(QNearFieldTagType1); + + d->m_pendingInternalCommands.insert(id, command); + + return id; +} + +/*! + Writes a single \a data byte to the linear byte \a address on the tag. If \a mode is + EraseAndWrite the byte will be erased before writing. If \a mode is WriteOnly the contents will + not be erased before writing. This is equivelant to writing the result of the bitwise OR of + \a data and the original value. + + Returns a request id which can be used to track the completion status of the request. + + Once the request completes the response can be retrieved from the requestResponse() function. + The response of this request will be a boolean value, true for success; otherwise false. + + \sa requestCompleted(), waitForRequestCompleted() +*/ +QNearFieldTarget::RequestId QNearFieldTagType1::writeByte(quint8 address, quint8 data, + WriteMode mode) +{ + if (address & 0x80) + return RequestId(); + + QByteArray command; + + if (mode == EraseAndWrite) + command.append(char(0x53)); // WRITE-E + else if (mode == WriteOnly) + command.append(char(0x1a)); // WRITE-NE + else + return RequestId(); + + command.append(char(address)); // Address + command.append(char(data)); // Data + command.append(uid().left(4)); // 4 bytes of UID + + RequestId id = sendCommand(command); + + Q_D(QNearFieldTagType1); + + d->m_pendingInternalCommands.insert(id, command); + + return id; +} + +/*! + Requests 128 bytes of data from the segment specified by \a segmentAddress. Returns a request + id which can be used to track the completion status of the request. + + Once the request completes successfully the response can be retrieved from the + requestResponse() function. The response of this request will be a QByteArray. + + \sa requestCompleted(), waitForRequestCompleted() +*/ +QNearFieldTarget::RequestId QNearFieldTagType1::readSegment(quint8 segmentAddress) +{ + if (segmentAddress & 0xf0) + return RequestId(); + + QByteArray command; + command.append(char(0x10)); // RSEG + command.append(char(segmentAddress << 4)); // Segment address + command.append(QByteArray(8, char(0x00))); // Data (unused) + command.append(uid().left(4)); // 4 bytes of UID + + RequestId id = sendCommand(command); + + Q_D(QNearFieldTagType1); + + d->m_pendingInternalCommands.insert(id, command); + + return id; +} + +/*! + Requests 8 bytes of data from the block specified by \a blockAddress. Returns a request id + which can be used to track the completion status of the request. + + Once the request completes successfully the response can be retrieved from the + requestResponse() function. The response of this request will be a QByteArray. + + \sa requestCompleted(), waitForRequestCompleted() +*/ +QNearFieldTarget::RequestId QNearFieldTagType1::readBlock(quint8 blockAddress) +{ + QByteArray command; + command.append(char(0x02)); // READ8 + command.append(char(blockAddress)); // Block address + command.append(QByteArray(8, char(0x00))); // Data (unused) + command.append(uid().left(4)); // 4 bytes of UID + + RequestId id = sendCommand(command); + + Q_D(QNearFieldTagType1); + + d->m_pendingInternalCommands.insert(id, command); + + return id; +} + +/*! + Writes 8 bytes of \a data to the block specified by \a blockAddress. If \a mode is + EraseAndWrite the bytes will be erased before writing. If \a mode is WriteOnly the contents + will not be erased before writing. This is equivelant to writing the result of the bitwise OR + of \a data and the original value. + + Returns a request id which can be used to track the completion status of the request. + + Once the request completes the response can be retrieved from the requestResponse() function. + The response of this request will be a boolean value, true for success; otherwise false. + + \sa requestCompleted(), waitForRequestCompleted() +*/ +QNearFieldTarget::RequestId QNearFieldTagType1::writeBlock(quint8 blockAddress, + const QByteArray &data, + WriteMode mode) +{ + if (data.length() != 8) + return RequestId(); + + QByteArray command; + + if (mode == EraseAndWrite) + command.append(char(0x54)); // WRITE-E8 + else if (mode == WriteOnly) + command.append(char(0x1b)); // WRITE-NE8 + else + return RequestId(); + + command.append(char(blockAddress)); // Block address + command.append(data); // Data + command.append(uid().left(4)); // 4 bytes of UID + + RequestId id = sendCommand(command); + + Q_D(QNearFieldTagType1); + + d->m_pendingInternalCommands.insert(id, command); + + return id; +} + +/*! + \reimp +*/ +bool QNearFieldTagType1::handleResponse(const QNearFieldTarget::RequestId &id, + const QByteArray &response) +{ + Q_D(QNearFieldTagType1); + + bool handled; + + if (d->m_pendingInternalCommands.contains(id)) { + const QByteArray command = d->m_pendingInternalCommands.take(id); + + QVariant decodedResponse = decodeResponse(command, response); + setResponseForRequest(id, decodedResponse); + + handled = true; + } else { + handled = QNearFieldTarget::handleResponse(id, response); + } + + // continue reading / writing NDEF message + if (d->m_nextExpectedRequestId == id) { + if (d->m_readNdefMessageState != QNearFieldTagType1Private::NotReadingNdefMessage) + d->progressToNextNdefReadMessageState(); + else if (d->m_writeNdefMessageState != QNearFieldTagType1Private::NotWritingNdefMessage) + d->progressToNextNdefWriteMessageState(); + } + + return handled; +} + +#include "moc_qnearfieldtagtype1.cpp" |