📜 ⬆️ ⬇️

Read and write NFC Tag on MeeGo Harmattan

This post participates in the competition " Smart phones for smart posts "

Introduction


What is NFC?

According to Wikipedia, NFC (Near Field Communication) is a short-range wireless high-frequency communication technology that allows data to be exchanged between devices located at a distance of about 10 centimeters.

There are three most popular options for using NFC technology in mobile phones:
card emulation - the phone pretends to be a card, such as a pass or a payment card;
Read mode - the phone reads a passive tag (Tag), for example, for interactive advertising;
P2P mode - two phones communicate and exchange information.
')
We will consider the second method of use, namely reading a passive tag, moreover, we will also learn how to record information on such tags using a phone

What a story?

I will talk not only about the methods of working with NFC, but also about the user interface developed by me specifically for this article program. That is, in the process of reading you will pass the full path of creating an application for working with the NFC Tag for MeeGo Harmattan.

Table of contents




Qt Ambassador
UPDATE: Today, 12/20/2011 , a letter came that the application was received in Qt Ambassador
UPDATE: The night passed and the project was published in the program: Qt Ambassador Showcase



What is the NFC Tag?

NFC Tag is our passive tag. The picture shows the appearance of how it may look, that is, as a rule, it is a sticker made of thick paper, which has a microchip and an antenna made of foil. NFC tags are of several types, the type of maximum data size also depends on the type. I am the proud owner of several Type 2 labels, 192 bytes, brought from Qt Developer Days 2011 . Well, 192 bytes are not thick, but enough for our experiments.

Program logic


Force the application to intercept NFC processing. Tags

So, in order to start processing tags, we need an object of class QNdefManager
NfcManager::NfcManager(QObject *parent) : QObject(parent), m_manager(new QNearFieldManager(this)), m_cachedTarget(0), m_mode(NfcManager::Read) { connect(m_manager, SIGNAL(targetDetected(QNearFieldTarget*)), this, SLOT(targetDetected(QNearFieldTarget*))); connect(m_manager, SIGNAL(targetLost(QNearFieldTarget*)), this, SLOT(targetLost(QNearFieldTarget*))); m_manager->setTargetAccessModes(QNearFieldManager::NdefReadTargetAccess | QNearFieldManager::NdefWriteTargetAccess); } 

Create it in the constructor of our class NfcManager , which we will use to work with NFC. We definitely need to connect the targetDetected and targetLost signals of this object to our slots, which, in fact, will be the main event handlers for the appearance of a tag in the field of view of the phone. In the third line of the constructor, we set the read and write mode so that we can not only read but also write tags.

Interceptor

Now consider the described slots:
 void NfcManager::targetDetected(QNearFieldTarget *target) { if (m_cachedTarget) delete m_cachedTarget; m_cachedTarget = target; if (m_mode == Read) readTarget(m_cachedTarget); if (m_mode == Write) writeTarget(m_cachedTarget); } 

When a tag is detected, we just in case save a pointer to an object of type QNearFieldTarget , which is the program interface to the tag itself.
Then there are two conditions and, depending on the mode (read or write), call the appropriate processing methods. From the point of view of beautiful architecture, this is not the best solution, but I did it intentionally, so as not to complicate the code.

 void NfcManager::targetLost(QNearFieldTarget *target) { m_cachedTarget = 0; target->deleteLater(); } 

If you lose a tag, we simply release the resources that are used.

Reading

Now consider the tag reading methods:
 void NfcManager::readTarget(QNearFieldTarget *target) { connect(target, SIGNAL(error(QNearFieldTarget::Error,QNearFieldTarget::RequestId)), this, SLOT(errorHandler(QNearFieldTarget::Error,QNearFieldTarget::RequestId))); connect(target, SIGNAL(ndefMessageRead(QNdefMessage)), this, SLOT(readRecords(QNdefMessage))); target->readNdefMessages(); } 

Reading takes place in asynchronous mode, so in this method we simply connect the error handling signal and the read completion signal, which will be called only if the reading has occurred without errors.
After that, we simply call the method to read:
 void NfcManager::readRecords(const QNdefMessage &message) { if (message.isEmpty()) return; QNdefRecord record = message.at(0); // Read only first readRecord(record); } 

If the reading was successful, we will get into this slot, where we will get the first record from the list of records present on the tag.
Yes, according to the specification there can be several entries on the tag, but as the documentation says, for Symbian and Harmattan only one entry is read and written.

 void NfcManager::readRecord(const QtMobility::QNdefRecord &record) { DataContainer *result = 0; if (record.isRecordType<QNdefNfcUriRecord>()) { QNdefNfcUriRecord uriRecord(record); result = new UriDataContainer(uriRecord.payload(), uriRecord.uri().toString()); } else if (record.isRecordType<QNdefNfcTextRecord>()) { QNdefNfcTextRecord textRecord(record); result = new TextDataContainer(textRecord.payload(), textRecord.text()); } else if (record.isRecordType<NdefNfcSmartPosterRecord>()) { NdefNfcSmartPosterRecord smartPosterRecord(record); result = new SmartPosterDataContainer(smartPosterRecord.payload(), smartPosterRecord.uri().toString(), smartPosterRecord.title()); } else { result = new DataContainer(record.payload()); } emit tagReadFinished(result); } 

And so, after several transitions on auxiliary methods, we got to the most important method, which turns the information encoded into tags into letters familiar to us.
At the moment, Qt Mobility out of the box only supports two types of entries: links ( Uri ) and text ( Text ), to the third type - Smart Poster, we will return below.
As you can see, the data from the record is immediately placed in a new object, these are simple objects that I specifically created to facilitate the transfer of data in QML

At the end, a signal containing the data object is called. In the future, we will catch this signal in QML.

Record

 void NfcManager::setDataForWrite(const QString &text, const QString &uri) { m_textForWrite = text; m_uriForWrite = uri; } 

This method must be called before attempting to write in order to set new values ​​for Uri and / or Text. If you do not call it on the tag will be recorded previous data (this approach is useful if you want to write a lot of tags of the same type)

 void NfcManager::writeTarget(QNearFieldTarget *target) { if (m_textForWrite.isEmpty() && m_uriForWrite.isEmpty()) return; m_cachedTarget = target; QNdefMessage message; if (!m_textForWrite.isEmpty() && !m_uriForWrite.isEmpty()) { NdefNfcSmartPosterRecord smartPosterRecord; smartPosterRecord.setTitle(m_textForWrite); smartPosterRecord.setUri(QUrl(m_uriForWrite)); message.append(smartPosterRecord); } else if (!m_textForWrite.isEmpty()) { QNdefNfcTextRecord textRecord; textRecord.setText(m_textForWrite); message.append(textRecord); } else { QNdefNfcUriRecord uriRecord; uriRecord.setUri(QUrl(m_uriForWrite)); message.append(uriRecord); } connect(target, SIGNAL(error(QNearFieldTarget::Error,QNearFieldTarget::RequestId)), this, SLOT(errorHandler(QNearFieldTarget::Error,QNearFieldTarget::RequestId))); connect(target, SIGNAL(ndefMessagesWritten()), this, SIGNAL(tagWriteFinished())); target->writeNdefMessages(QList<QNdefMessage>() << message); } 

The main writing method is as simple as reading. In the condition block, we simply select the type of record. If only Uri or Text is present, then the corresponding type is created, if both fields are filled in, a Smart Poster type record is created.
After that, we re-enable the error handler. But pay attention, because in the backend we do not need any processing logic to successfully complete the read, we forward the signal to the signal, which we will later catch in QML.

Smart Poster, what is it?

So, the Smart Poster is a special kind of NFC recording that can simultaneously contain a link, a text title (in several languages), graphic icons in jpeg or png formats, and even an animated icon in mpeg format.
In addition, there may be two more fields:
Action - tells the phone which application and how to open uriRecord for processing
Size is a simple integer that displays the size of the downloadable content by reference.

We write our class for Smart Poster

Below I will tell you how to create your own type of NDEF entry using the example of creating a type for a smart poster entry.
Immediately make a reservation that my type is simplified. It does not support neither Action nor Size, nor even icons, but it allows you to simultaneously store text and a link.

This is the announcement for the class of our Smart Poster:
 class NdefNfcSmartPosterRecord : public QNdefRecord { public: Q_DECLARE_NDEF_RECORD(NdefNfcSmartPosterRecord, QNdefRecord::NfcRtd, "Sp", QByteArray()) void setTitle(const QString &title, const QString &locale = "en"); void setUri(const QUrl &uri); QString title(const QString &locale = "en") const; QUrl uri() const; //TODO: Add icon, action and size fields support private: RecordPart readPart(int &offset) const; }; Q_DECLARE_ISRECORDTYPE_FOR_NDEF_RECORD(NdefNfcSmartPosterRecord, QNdefRecord::NfcRtd, "Sp") 

So, the Qt Mobility developers have already taken care to make it easier for us to live, and have created two special macros that do all the most rough work.

Parameters for macros are: class name, record type (for Smart Poster, this is QNdefRecord :: NfcRtd ) and “Type Name” - an abbreviation for recognition in the tag. And also the last parameter in Q_DECLARE_NDEF_RECORD is the data for the initial initialization of the data, in our case it is an empty byte array.

Now look at the implementation of the methods of reading and writing.

Simple structure for storing a disassembled part of a record
 struct RecordPart { enum Type { Uri, Text, Action, Icon, Size, Unknown }; Type type; QString text; QString locale; // For text type quint8 prefix; // For Uri type RecordPart() : type(Unknown), text(QString()), locale(QString()), prefix(0) { } }; 


First, consider the methods for reading:
 static const char * const abbreviations[] = { 0, "http://www.", "https://www.", "http://", //    "urn:epc:", "urn:nfc:", }; 

An array of various prefixes for uri supported by the specification.

 QUrl NdefNfcSmartPosterRecord::uri() const { const QByteArray p = payload(); if (p.isEmpty()) return QUrl(); if (p.isEmpty()) return QString(); int offset = 0; QString uri; while (offset < p.size()) { RecordPart part = readPart(offset); if (part.type == RecordPart::Uri) { if (part.prefix > 0 && part.prefix < (sizeof(abbreviations) / sizeof(*abbreviations))) uri = QString(abbreviations[part.prefix]) + part.text; } } if (uri.isEmpty()) return QUrl(); return QUrl(uri); } 

The uri read method is fairly simple at first glance - we load all the bytes read from the record into p , and then read the parts in the array until we find the Uri type part (according to the specification it can be only one)
The “magic” readPart method will be discussed below.

 QString NdefNfcSmartPosterRecord::title(const QString &locale) const { const QByteArray p = payload(); if (p.isEmpty()) return QString(); int offset = 0; QMap<QString, QString> title; while (offset < p.size()) { RecordPart part = readPart(offset); if (part.type == RecordPart::Text) { title.insert(part.locale, part.text); } } if (title.isEmpty()) return QString(); if (title.contains(locale)) return title.value(locale); if (title.contains("en")) return title.value("en"); return title.constBegin().value(); } 

The method for title differs only in that there may be many titles in different languages. Therefore, we first select them all, and then try to find the right one.

All the magic happens in the readPart method, which turns the internal recording format into a simple and clear structure RecordPart
 RecordPart NdefNfcSmartPosterRecord::readPart(int &offset) const { RecordPart result; const QByteArray p = payload(); ..... //This block has pointer arithmetic, don't edit quint8 typeLength = p[++offset]; quint8 payloadLength = p[++offset]; QString type = QString(p.mid(++offset, typeLength)); offset += typeLength - 1; if (type == "U") { result.type = RecordPart::Uri; result.prefix = p[++offset]; result.text = QString(p.mid(++offset, payloadLength - 1)); offset += payloadLength - 1; } if (type == "T") { result.type = RecordPart::Text; quint8 localeLength = p[++offset]; result.locale = QString(p.mid(++offset, localeLength)); // 5 bytes of locale string offset += localeLength - 1; result.text = QString(p.mid(++offset, payloadLength - 1 - localeLength)); offset += payloadLength - 1 - localeLength; } ..... //TODO: Add handler for icon return result; } 

The title of each block consists of:
1 byte, technical flags, in our simplified class we will not check the integrity, etc., so we just skip this byte;
2 bytes, the length of the string with the "Type Name";
3 bytes, the length of the main field with information;
4-n bytes, the string with the “Type Name” is a string type identifier, for Text is 'T', for Uri it is 'U'.
Next comes the main data block.

For Uri, these are just two fields.
1 byte for the prefix number from the array above and everything else on the body of the link.

For Text, these are three fields:
1 byte status field, which contains additional flags and the length of the locale dates.
Locale string, for example, " en " or " ru-RU "
And, actually, the text itself.

Note that this method takes the offset from a non-constant link, and modifies it, thus allowing us to go from one record to another in a loop.

Now let's talk about recording methods. For simplicity, consider only setUri . The method for the title is relatively identical.
 void NdefNfcSmartPosterRecord::setUri(const QUrl &uri) { //Don't edit - pointer arithmetic QByteArray p; int abbrevs = sizeof(abbreviations) / sizeof(*abbreviations); for (int i = 1; i < abbrevs; ++i) { if (uri.toString().startsWith(QLatin1String(abbreviations[i]))) { p[0] = i; p += uri.toString().mid(qstrlen(abbreviations[i])).toUtf8(); } } QByteArray oldPayload = payload(); QByteArray uHeader(4, 0); uHeader[0] = 0b01 + 0b00010000; uHeader[1] = 1; uHeader[2] = p.size(); uHeader[3] = 'U'; if (!oldPayload.isEmpty()) { uHeader[0] = uHeader[0] + 0b10000000; // change MB flag here oldPayload[0] = oldPayload[0] & 0b01111111; } if (oldPayload.isEmpty()) { uHeader[0] = uHeader[0] + 0b10000000 + 0b01000000; } p.prepend(uHeader); p.append(oldPayload); setPayload(p); } 

The complexity of installation methods is that it is necessary to take into account the case when some part of the Smart Poster (for example, Text) is already installed, and now you also need to install Uri. And this means that we must keep the old payload and add a new one. It would seem that there is no problem in the concatenation of two QByteArray , but the very first bytes with flags come into play, the fact is that we need to modify the flag of the first part ( MB ) when adding a new one.
This is what this line of code does:
 // change MB flag here oldPayload[0] = oldPayload[0] & 0b01111111; 

As you can see, we add a new part before the old one, not after. This is only because if we added to the end, we’ll search for the flag of the latter part and its modifications ( ME )
would have to run around the old payload.

That's all about the Smart Poster and the NFC in general.

Program interface


Page and PageStack

The main idea of ​​mobile QML applications is switching screens in the queue. In terms of Qt components, screens are called pages, and the main container is called a window.
 import QtQuick 1.1 import com.nokia.meego 1.0 PageStackWindow { id: appWindow initialPage: mainPage MainPage { id: mainPage } } 

main.qml here creates a container-window and specifies the main page as the initialization page.

 Page { id: mainPage ..... Header { id: header anchors { top: parent.top right: parent.right left: parent.left } } ..... 

This is the page description. By the way, if you noticed, all standard applications from Nokia have a neat color header. So, there is no standard component for this header, despite the fact that they encourage you to use it everywhere in your UI Guidelines .

To navigate between pages, an object of type PageStack is used ; any Page has a pointer to an instance of this class named pageStack . Thus, to go to a new page we must use the construction
 pageStack.push(Qt.resolvedUrl("NewPage.qml")) 


and to return to the previous one:
 pageStack.pop() 

By the way, if for the pop method you set the identifier of a specific page as a parameter, then you can go back not only to the page back, but also to any arbitrary on the stack.

Listview

On the main screen, we can observe a list of actions that can be performed; a similar list is made like this:
 ListView { id: actionList .... delegate: ListDelegate { anchors { left : parent.left leftMargin: 20 } onClicked: { pageStack.push(Qt.resolvedUrl(model.source)) } MoreIndicator { anchors { verticalCenter: parent.verticalCenter right: parent.right rightMargin: 30 } } } model: ListModel { ListElement { title: "Read Tag" subtitle: "" source: "ReadPage.qml" } ...... } } 

The ListView element is the actual list itself, which has two key properties.
delegate is the delegate for drawing a single list item and model is the data model for the list.
The com.nokia.extras package contains the ListDelegate component that is already ready to create a simple delegate. The ListModel element allows you to specify a simple data model. And ListElement is nothing more than one record of this model.

Toolbar

For various actions, the mobile application may also have a Toolbar with icons, my application is simple and the toolbar on the internal pages contains only a back button
 Page { id: readPage ..... tools: ToolBarLayout { ToolIcon { iconId: "toolbar-back" onClicked: { pageStack.pop() } } } ..... 

In order to connect the toolbar to the page, it must be assigned to the tools property, which is null by default.

Label and TextField

To display text, you can use the Label component - this is nothing more than a stylized wrapper over a standard Text element.
 Label { id: touchLabel ..... font.pixelSize: 60 text: qsTr("Touch a tag") } 


And for the input field, use TextField - this is an advanced wrapper over the standard TextInput
 TextField { id: textEdit ..... placeholderText: qsTr("Text") text: "yandex" } 


InfoBanner

In case an error occurred while reading / writing the tag, we must somehow inform the user about it and ask to bring the label to the phone again, for this you can use the InfoBanner element
 InfoBanner{ id: errorBanner timerEnabled: true timerShowTime: 3 * 1000 topMargin: header.height + 20 leftMargin: 20 } 


We connect all together


We considered separately all the main QML components that will be required for our application, as well as all the necessary program logic. It is time to tie both parts together.

setContextProperty

In order for our QML code to see our class for managing reading and writing, we need to inform the declarative engine about the existence of an object of this class, so we write in main.cpp:
 NfcManager *nfcManager = new NfcManager(); viewer->rootContext()->setContextProperty("NfcManager", nfcManager); 

That is, we create an NfcManager object and indicate to the engine that we should have access to it from QML.

By the way, in the last QtSDK update, something was broken, and in order for this code to work correctly, you need to use the workaround described in the bugtracker .

qmlRegisterType

As you, of course, remember after the label has been read, we emit a signal containing the object with the received data. In order for this object to be available in QML we must register the class of this object in QML
 qmlRegisterType<DataContainer>(); qmlRegisterType<UriDataContainer>(); qmlRegisterType<TextDataContainer>(); qmlRegisterType<SmartPosterDataContainer>(); 

By inserting this code into main.cpp , we register data classes for all types of data we have.
However, it is forbidden to create such objects directly from QML.

Interaction

When a user hits a page to write or read a label, we need to execute the following code:

For reading
 function tagWasRead(container) { NfcManager.stopDetection() readPage.dataContainer = container pageStack.push(Qt.resolvedUrl("ReadResultPage.qml"), {dataContainer: readPage.dataContainer}) } function readError(string) { errorBanner.text = string errorBanner.show() } Component.onCompleted: { NfcManager.tagReadFinished.connect(readPage.tagWasRead) NfcManager.accessError.connect(readPage.readError) NfcManager.setReadMode() NfcManager.startDetection() } 

The Component.onCompleted method runs when the page is fully created. In this method, we hook handlers for errors and for a successful result to our signals from NfcManager (note the syntax for connecting a C ++ signal to a QML slot)
After, we set the mode for reading and tell our manager what to expect when the tag is attached.

Also note the push call.
 pageStack.push(Qt.resolvedUrl("ReadResultPage.qml"), {dataContainer: readPage.dataContainer}) 

the second parameter allows us to transfer the container with the data to the next page, which will simply process it

example:
 ..... Label { id: rawDataLabel width: parent.width font.pixelSize: 30 font.family: "Courier New" text: readPage.dataContainer.rawHexData() wrapMode: Text.WrapAnywhere } ..... 


To record
 function tagWasWritten() { ..... } function writeError(string) { ..... } Component.onCompleted: { NfcManager.tagWriteFinished.connect(writePage.tagWasWritten) NfcManager.accessError.connect(writePage.writeError) NfcManager.setWriteMode() NfcManager.setDataForWrite(writePage.text, writePage.uri) NfcManager.startDetection() } 

Very similar, isn't it? The only difference is the call to the setDataForWrite method, which passes the data for writing.

Conclusion


So we got a simple but functional application for the MeeGo Harmattan platform. However, with minimal effort you can turn it into an application for Symbian. As far as I know, some Symbian phones ( C7 for example) also have an integrated NFC chip.
I would also like to add that formally on the NFC Tag you can record information in any format that will make it understandable only for your application. So you can think of many more ways to use this technology.

What to read

If you are interested in this topic, I recommend to familiarize yourself with the official specifications of NFC and NDEF. They can be downloaded on demand absolutely free of charge from this page .
Qt Connectivity documentation is included in QtSDK, but sometimes, for example, when developing your own QNdefRecord format, it is not enough, then you are welcome to Qt Mobility source code - you can learn a lot of interesting things there.
According to MeeGo, Qt Components also has official documentation in QtSDK, but sometimes it leaves much to be desired, I recommend reading the qt-components-examples code that can be found here .

Additionally

I'm going to continue to develop the app, and this is probably not the last post on the NFC Tag.
To stay updated you can watch the project on gitorius
Or subscribe to my blog, the link to which can be found in the profile.
In the near future, I plan to place the app in the Nokia Store , so look for it there.
Now the deb package can be downloaded here .

Acknowledgments

I express my gratitude for reading the text of the article for errors and misprints for habra users:
dreary_eyes and tass .

Source: https://habr.com/ru/post/133471/


All Articles