Add support for NFC tag writing and reading

pull/451/head
Michael Zanetti 2020-10-14 18:59:07 +02:00
parent 621bfaed91
commit c36000c1e9
10 changed files with 330 additions and 248 deletions

View File

@ -30,6 +30,7 @@ SOURCES += \
../nymea-app/stylecontroller.cpp \
../nymea-app/platformhelper.cpp \
../nymea-app/nfchelper.cpp \
../nymea-app/nfcthingactionwriter.cpp \
../nymea-app/platformintegration/android/platformhelperandroid.cpp \
service_main.cpp
@ -40,6 +41,7 @@ HEADERS += \
../nymea-app/stylecontroller.h \
../nymea-app/platformhelper.h \
../nymea-app/nfchelper.h \
../nymea-app/nfcthingactionwriter.h \
../nymea-app/platformintegration/android/platformhelperandroid.h \
DISTFILES += \

View File

@ -7,6 +7,7 @@
#include "../nymea-app/stylecontroller.h"
#include "../nymea-app/platformhelper.h"
#include "../nymea-app/nfchelper.h"
#include "../nymea-app/nfcthingactionwriter.h"
#include "../nymea-app/platformintegration/android/platformhelperandroid.h"
#include <QQmlApplicationEngine>
@ -41,7 +42,8 @@ DeviceControlApplication::DeviceControlApplication(int argc, char *argv[]) : QAp
registerQmlTypes();
qmlRegisterSingletonType<PlatformHelper>("Nymea", 1, 0, "PlatformHelper", platformHelperProvider);
qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" );
qmlRegisterType<NfcHelper>("Nymea", 1, 0, "NfcHelper");
qmlRegisterType<NfcThingActionWriter>("Nymea", 1, 0, "NfcThingActionWriter");
qmlRegisterSingletonType<NfcHelper>("Nymea", 1, 0, "NfcHelper", NfcHelper::nfcHelperProvider);
StyleController *styleController = new StyleController(this);
m_qmlEngine->rootContext()->setContextProperty("styleController", styleController);
@ -168,6 +170,11 @@ void DeviceControlApplication::runNfcAction()
continue;
}
if (parts.count() > 2) {
// The parameters might contain a #, let's merge them again
parts[1] = parts.mid(1).join('#');
}
QString actionTypeName = parts.at(0);
ActionType *actionType = thing->thingClass()->actionTypes()->findByName(actionTypeName);
if (!actionType) {
@ -188,6 +195,8 @@ void DeviceControlApplication::runNfcAction()
}
}
qDebug() << "Parameters in NFC uri:" << paramsInUri;
QVariantList params;
for (int j = 0; j < actionType->paramTypes()->rowCount(); j++) {
ParamType *paramType = actionType->paramTypes()->get(j);
@ -201,6 +210,8 @@ void DeviceControlApplication::runNfcAction()
params.append(param);
}
qDebug() << "Action parameters:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
m_engine->thingManager()->executeAction(thingId, actionType->id(), params);
}
}

View File

@ -53,6 +53,7 @@
#include "applogcontroller.h"
#include "ruletemplates/messages.h"
#include "nfchelper.h"
#include "nfcthingactionwriter.h"
QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
@ -67,7 +68,6 @@ QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
#endif
}
int main(int argc, char *argv[])
{
@ -125,7 +125,8 @@ int main(int argc, char *argv[])
QQmlApplicationEngine *engine = new QQmlApplicationEngine();
qmlRegisterSingletonType<PlatformHelper>("Nymea", 1, 0, "PlatformHelper", platformHelperProvider);
qmlRegisterType<NfcHelper>("Nymea", 1, 0, "NfcHelper");
qmlRegisterSingletonType<NfcHelper>("Nymea", 1, 0, "NfcHelper", NfcHelper::nfcHelperProvider);
qmlRegisterType<NfcThingActionWriter>("Nymea", 1, 0, "NfcThingActionWriter");
PushNotifications::instance()->connectClient();
qmlRegisterSingletonType<PushNotifications>("Nymea", 1, 0, "PushNotifications", PushNotifications::pushNotificationsProvider);

View File

@ -1,181 +1,28 @@
#include "nfchelper.h"
#include "types/deviceclass.h"
#include "types/statetype.h"
#include "types/ruleaction.h"
#include "types/ruleactionparams.h"
#include "types/ruleactionparam.h"
#include <QNearFieldManager>
#include <QNdefMessage>
#include <QDebug>
#include <QNdefNfcUriRecord>
#include <QUrl>
#include <QUrlQuery>
NfcHelper::NfcHelper(QObject *parent):
QObject(parent),
m_manager(new QNearFieldManager(this)),
m_actions(new RuleActions(this))
NfcHelper::NfcHelper(QObject *parent) : QObject(parent)
{
connect(m_manager, &QNearFieldManager::targetDetected, this, &NfcHelper::targetDetected);
connect(m_manager, &QNearFieldManager::targetLost, this, &NfcHelper::targetLost);
connect(m_actions, &RuleActions::countChanged, this, &NfcHelper::updateContent);
m_manager->startTargetDetection();
}
NfcHelper::~NfcHelper()
NfcHelper *NfcHelper::instance()
{
m_manager->stopTargetDetection();
}
Engine *NfcHelper::engine() const
{
return m_engine;
}
void NfcHelper::setEngine(Engine *engine)
{
if (m_engine != engine) {
m_engine = engine;
emit engineChanged();
updateContent();
static NfcHelper *thiz = nullptr;
if (!thiz) {
thiz = new NfcHelper();
}
return thiz;
}
Device *NfcHelper::thing() const
QObject *NfcHelper::nfcHelperProvider(QQmlEngine */*engine*/, QJSEngine */*scriptEngine*/)
{
return m_thing;
return instance();
}
void NfcHelper::setThing(Device *thing)
bool NfcHelper::isAvailable() const
{
if (m_thing != thing) {
m_thing = thing;
emit thingChanged();
updateContent();
}
QNearFieldManager manager;
return manager.isAvailable();
}
RuleActions *NfcHelper::actions() const
{
return m_actions;
}
int NfcHelper::messageSize() const
{
return m_currentMessage.toByteArray().size();
int ret = 0;
for (int i = 0; i < m_currentMessage.size(); i++) {
ret += m_currentMessage.at(i).payload().size();
}
return ret;
}
NfcHelper::TagStatus NfcHelper::status() const
{
return m_status;
}
void NfcHelper::updateContent()
{
qDebug() << "Updating" << m_engine << m_thing;
// Creating an URI type record with this format:
// nymea://<nymeaId>
// ? t=<thingId>
// & a[0]=<actionTypeName>
// & a[1]=<actionTypeName>#<paramName1>:<paramValue>
// & a[2]=<actionTypeName>#<paramName1>:<paramValue>+<paramName2>:<paramValue>
// & ...
// NOTE: We're using actionType and paramType *name* instead of the ID because NFC tags are
// small and normally names are shorter than ids so we save some space.
// NOTE: param values are percentage encoded to prevent messing with the parsing if they
// contain + or :
QUrl url;
url.setScheme("nymea");
if (!m_engine || !m_thing) {
return;
}
url.setHost(m_engine->jsonRpcClient()->currentHost()->uuid().toString().remove(QRegExp("[{}]")));
QUrlQuery query;
query.addQueryItem("t", m_thing->id().toString().remove(QRegExp("[{}]")));
for (int i = 0; i < m_actions->rowCount(); i++) {
RuleAction *action = m_actions->get(i);
QStringList params;
ActionType *at = m_thing->thingClass()->actionTypes()->getActionType(action->actionTypeId());
if (!at) {
qWarning() << "ActionType not found in thing" << action->actionTypeId();
continue;
}
for (int j = 0; j < action->ruleActionParams()->rowCount(); j++) {
RuleActionParam *param = action->ruleActionParams()->get(j);
ParamType *pt = at->paramTypes()->getParamType(param->paramTypeId());
if (!pt) {
qWarning() << "ParamType not found in thing";
continue;
}
params.append(pt->name() + ":" + param->value().toByteArray().toPercentEncoding());
}
QString actionString = at->name();
if (params.length() > 0) {
actionString += "#" + params.join("+");
}
query.addQueryItem(QString("a[%1]").arg(i), actionString);
}
url.setQuery(query);
qDebug() << "writing message" << url;
QNdefNfcUriRecord record;
record.setUri(url);
QNdefMessage message;
message.append(record);
m_currentMessage = message;
emit messageSizeChanged();
}
void NfcHelper::targetDetected(QNearFieldTarget *target)
{
QDateTime startTime = QDateTime::currentDateTime();
qDebug() << "target detected";
connect(target, &QNearFieldTarget::error, this, [=](QNearFieldTarget::Error error, const QNearFieldTarget::RequestId &id){
qDebug() << "Tag error:" << error;
m_status = TagStatusFailed;
emit statusChanged();
});
connect(target, &QNearFieldTarget::ndefMessagesWritten, this, [=](){
qDebug() << "Tag written in" << startTime.msecsTo(QDateTime::currentDateTime());
m_status = TagStatusWritten;
emit statusChanged();
});
QNearFieldTarget::RequestId m_request = target->writeNdefMessages(QList<QNdefMessage>() << m_currentMessage);
if (!m_request.isValid()) {
qDebug() << "Error writing tag";
m_status = TagStatusFailed;
emit statusChanged();
}
m_status = TagStatusWriting;
emit statusChanged();
}
void NfcHelper::targetLost(QNearFieldTarget *target)
{
qDebug() << "Target lost" << target;
m_status = TagStatusWaiting;
emit statusChanged();
}

View File

@ -2,70 +2,22 @@
#define NFCHELPER_H
#include <QObject>
#include <QNearFieldManager>
#include <QNdefMessage>
#include "types/device.h"
#include "engine.h"
#include "types/ruleactions.h"
#include <QQmlEngine>
class NfcHelper : public QObject
{
Q_OBJECT
Q_PROPERTY(Engine *engine READ engine WRITE setEngine NOTIFY engineChanged)
Q_PROPERTY(Device *thing READ thing WRITE setThing NOTIFY thingChanged)
Q_PROPERTY(RuleActions *actions READ actions CONSTANT)
Q_PROPERTY(int messageSize READ messageSize NOTIFY messageSizeChanged)
Q_PROPERTY(TagStatus status READ status NOTIFY statusChanged)
Q_PROPERTY(bool isAvailable READ isAvailable CONSTANT)
public:
enum TagStatus {
TagStatusWaiting,
TagStatusWriting,
TagStatusWritten,
TagStatusFailed
};
Q_ENUM(TagStatus)
static NfcHelper* instance();
static QObject *nfcHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
explicit NfcHelper(QObject *parent = nullptr);
~NfcHelper();
Engine *engine() const;
void setEngine(Engine *engine);
Device *thing() const;
void setThing(Device *thing);
RuleActions *actions() const;
int messageSize() const;
TagStatus status() const;
signals:
void engineChanged();
void thingChanged();
void messageSizeChanged();
void statusChanged();
private slots:
void updateContent();
void targetDetected(QNearFieldTarget *target);
void targetLost(QNearFieldTarget *target);
bool isAvailable() const;
private:
QNearFieldManager *m_manager = nullptr;
Engine *m_engine = nullptr;
Device *m_thing = nullptr;
RuleActions* m_actions;
TagStatus m_status = TagStatusWaiting;
QNdefMessage m_currentMessage;
explicit NfcHelper(QObject *parent = nullptr);
};
#endif // NFCHELPER_H

View File

@ -0,0 +1,185 @@
#include "nfcthingactionwriter.h"
#include "types/deviceclass.h"
#include "types/statetype.h"
#include "types/ruleaction.h"
#include "types/ruleactionparams.h"
#include "types/ruleactionparam.h"
#include <QNearFieldManager>
#include <QNdefMessage>
#include <QDebug>
#include <QNdefNfcUriRecord>
#include <QUrl>
#include <QUrlQuery>
NfcThingActionWriter::NfcThingActionWriter(QObject *parent):
QObject(parent),
m_manager(new QNearFieldManager(this)),
m_actions(new RuleActions(this))
{
connect(m_manager, &QNearFieldManager::targetDetected, this, &NfcThingActionWriter::targetDetected);
connect(m_manager, &QNearFieldManager::targetLost, this, &NfcThingActionWriter::targetLost);
connect(m_actions, &RuleActions::countChanged, this, &NfcThingActionWriter::updateContent);
m_manager->startTargetDetection();
}
NfcThingActionWriter::~NfcThingActionWriter()
{
m_manager->stopTargetDetection();
}
bool NfcThingActionWriter::isAvailable() const
{
return m_manager->isAvailable();
}
Engine *NfcThingActionWriter::engine() const
{
return m_engine;
}
void NfcThingActionWriter::setEngine(Engine *engine)
{
if (m_engine != engine) {
m_engine = engine;
emit engineChanged();
updateContent();
}
}
Device *NfcThingActionWriter::thing() const
{
return m_thing;
}
void NfcThingActionWriter::setThing(Device *thing)
{
if (m_thing != thing) {
m_thing = thing;
emit thingChanged();
updateContent();
}
}
RuleActions *NfcThingActionWriter::actions() const
{
return m_actions;
}
int NfcThingActionWriter::messageSize() const
{
return m_currentMessage.toByteArray().size();
int ret = 0;
for (int i = 0; i < m_currentMessage.size(); i++) {
ret += m_currentMessage.at(i).payload().size();
}
return ret;
}
NfcThingActionWriter::TagStatus NfcThingActionWriter::status() const
{
return m_status;
}
void NfcThingActionWriter::updateContent()
{
qDebug() << "Updating" << m_engine << m_thing;
// Creating an URI type record with this format:
// nymea://<nymeaId>
// ? t=<thingId>
// & a[0]=<actionTypeName>
// & a[1]=<actionTypeName>#<paramName1>:<paramValue>
// & a[2]=<actionTypeName>#<paramName1>:<paramValue>+<paramName2>:<paramValue>
// & ...
// NOTE: We're using actionType and paramType *name* instead of the ID because NFC tags are
// small and normally names are shorter than ids so we save some space.
// NOTE: param values are percentage encoded to prevent messing with the parsing if they
// contain + or :
QUrl url;
url.setScheme("nymea");
if (!m_engine || !m_thing) {
return;
}
url.setHost(m_engine->jsonRpcClient()->currentHost()->uuid().toString().remove(QRegExp("[{}]")));
QUrlQuery query;
query.addQueryItem("t", m_thing->id().toString().remove(QRegExp("[{}]")));
for (int i = 0; i < m_actions->rowCount(); i++) {
RuleAction *action = m_actions->get(i);
QStringList params;
ActionType *at = m_thing->thingClass()->actionTypes()->getActionType(action->actionTypeId());
if (!at) {
qWarning() << "ActionType not found in thing" << action->actionTypeId();
continue;
}
for (int j = 0; j < action->ruleActionParams()->rowCount(); j++) {
RuleActionParam *param = action->ruleActionParams()->get(j);
ParamType *pt = at->paramTypes()->getParamType(param->paramTypeId());
if (!pt) {
qWarning() << "ParamType not found in thing";
continue;
}
params.append(pt->name() + ":" + param->value().toByteArray().toPercentEncoding());
}
QString actionString = at->name();
if (params.length() > 0) {
actionString += "#" + params.join("+");
}
query.addQueryItem(QString("a[%1]").arg(i), actionString);
}
url.setQuery(query);
qDebug() << "writing message" << url;
QNdefNfcUriRecord record;
record.setUri(url);
QNdefMessage message;
message.append(record);
m_currentMessage = message;
emit messageSizeChanged();
}
void NfcThingActionWriter::targetDetected(QNearFieldTarget *target)
{
QDateTime startTime = QDateTime::currentDateTime();
qDebug() << "target detected";
connect(target, &QNearFieldTarget::error, this, [=](QNearFieldTarget::Error error, const QNearFieldTarget::RequestId &id){
qDebug() << "Tag error:" << error;
m_status = TagStatusFailed;
emit statusChanged();
});
connect(target, &QNearFieldTarget::ndefMessagesWritten, this, [=](){
qDebug() << "Tag written in" << startTime.msecsTo(QDateTime::currentDateTime());
m_status = TagStatusWritten;
emit statusChanged();
});
QNearFieldTarget::RequestId m_request = target->writeNdefMessages(QList<QNdefMessage>() << m_currentMessage);
if (!m_request.isValid()) {
qDebug() << "Error writing tag";
m_status = TagStatusFailed;
emit statusChanged();
}
m_status = TagStatusWriting;
emit statusChanged();
}
void NfcThingActionWriter::targetLost(QNearFieldTarget *target)
{
qDebug() << "Target lost" << target;
m_status = TagStatusWaiting;
emit statusChanged();
}

View File

@ -0,0 +1,76 @@
#ifndef NFCTHINGACTIONWRITER_H
#define NFCTHINGACTIONWRITER_H
#include <QObject>
#include <QNearFieldManager>
#include <QNdefMessage>
#include "types/device.h"
#include "engine.h"
#include "types/ruleactions.h"
class NfcThingActionWriter : public QObject
{
Q_OBJECT
Q_PROPERTY(bool isAvailable READ isAvailable CONSTANT)
Q_PROPERTY(Engine *engine READ engine WRITE setEngine NOTIFY engineChanged)
Q_PROPERTY(Device *thing READ thing WRITE setThing NOTIFY thingChanged)
Q_PROPERTY(RuleActions *actions READ actions CONSTANT)
Q_PROPERTY(int messageSize READ messageSize NOTIFY messageSizeChanged)
Q_PROPERTY(TagStatus status READ status NOTIFY statusChanged)
public:
enum TagStatus {
TagStatusWaiting,
TagStatusWriting,
TagStatusWritten,
TagStatusFailed
};
Q_ENUM(TagStatus)
static NfcThingActionWriter *instance();
explicit NfcThingActionWriter(QObject *parent = nullptr);
~NfcThingActionWriter();
bool isAvailable() const;
Engine *engine() const;
void setEngine(Engine *engine);
Device *thing() const;
void setThing(Device *thing);
RuleActions *actions() const;
int messageSize() const;
TagStatus status() const;
signals:
void engineChanged();
void thingChanged();
void messageSizeChanged();
void statusChanged();
private slots:
void updateContent();
void targetDetected(QNearFieldTarget *target);
void targetLost(QNearFieldTarget *target);
private:
QNearFieldManager *m_manager = nullptr;
Engine *m_engine = nullptr;
Device *m_thing = nullptr;
RuleActions* m_actions;
TagStatus m_status = TagStatusWaiting;
QNdefMessage m_currentMessage;
};
#endif // NFCTHINGACTIONWRITER_H

View File

@ -15,6 +15,7 @@ PRE_TARGETDEPS += ../libnymea-app
HEADERS += \
mainmenumodel.h \
nfchelper.h \
nfcthingactionwriter.h \
platformintegration/generic/raspberrypihelper.h \
stylecontroller.h \
pushnotifications.h \
@ -26,6 +27,7 @@ HEADERS += \
SOURCES += main.cpp \
mainmenumodel.cpp \
nfchelper.cpp \
nfcthingactionwriter.cpp \
platformintegration/generic/raspberrypihelper.cpp \
stylecontroller.cpp \
pushnotifications.cpp \
@ -171,3 +173,5 @@ contains(ANDROID_TARGET_ARCH,) {
arm64-v8a
}
ANDROID_ABIS = armeabi-v7a arm64-v8a

View File

@ -112,14 +112,19 @@ Page {
}))
}
thingMenu.addItem(menuEntryComponent.createObject(thingMenu,
{
text: qsTr("Write NFC tag"),
iconSource: "../images/nfc.svg",
functionName: "writeNfcTag"
print("*** creating menu")
print("NFC", NfcHelper.isAvailable)
if (NfcHelper.isAvailable) {
thingMenu.addItem(menuEntryComponent.createObject(thingMenu,
{
text: qsTr("Write NFC tag"),
iconSource: "../images/nfc.svg",
functionName: "writeNfcTag"
}));
}));
}
}
function openDeviceMagicPage() {
pageStack.push(Qt.resolvedUrl("../magic/DeviceRulesPage.qml"), {device: root.device})
}

View File

@ -47,11 +47,10 @@ Page {
}
NfcHelper {
id: nfcHelper
NfcThingActionWriter {
id: nfcWriter
engine: _engine
thing: root.thing
}
// nfcHelper.writeThingStates(engine, root.thing)
@ -66,14 +65,14 @@ Page {
Label {
Layout.fillWidth: true
text: {
switch (nfcHelper.status) {
case NfcHelper.TagStatusWaiting:
switch (nfcWriter.status) {
case NfcThingActionWriter.TagStatusWaiting:
return qsTr("Tap an NFC tag to link it to %1.").arg(root.thing.name)
case NfcHelper.TagStatusWriting:
case NfcThingActionWriter.TagStatusWriting:
return qsTr("Writing NFC tag...")
case NfcHelper.TagStatusWritten:
case NfcThingActionWriter.TagStatusWritten:
return qsTr("NFC tag linked to %1.").arg(root.thing.name)
case NfcHelper.TagStatusFailed:
case NfcThingActionWriter.TagStatusFailed:
return qsTr("Failed linking the NFC tag to %1.").arg(root.thing.name)
}
}
@ -83,7 +82,7 @@ Page {
Label {
Layout.fillWidth: true
text: qsTr("Required tag size: %1 bytes").arg(nfcHelper.messageSize)
text: qsTr("Required tag size: %1 bytes").arg(nfcWriter.messageSize)
font.pixelSize: app.smallFont
horizontalAlignment: Text.AlignHCenter
enabled: false
@ -118,7 +117,7 @@ Page {
width: app.iconSize * 2
anchors.centerIn: parent
anchors.horizontalCenterOffset: - app.iconSize * 2
visible: nfcHelper.status == NfcHelper.TagStatusWaiting
visible: nfcWriter.status == NfcThingActionWriter.TagStatusWaiting
}
Item {
@ -128,7 +127,7 @@ Page {
scale: 1.5
anchors.centerIn: parent
anchors.horizontalCenterOffset: app.iconSize * 2
visible: nfcHelper.status == NfcHelper.TagStatusWaiting
visible: nfcWriter.status == NfcThingActionWriter.TagStatusWaiting
Rectangle {
anchors.fill: parent
@ -154,15 +153,15 @@ Page {
color: app.backgroundColor
border.width: 4
border.color: app.foregroundColor
opacity: nfcHelper.status == NfcHelper.TagStatusWaiting ? 0 : 1
opacity: nfcWriter.status == NfcThingActionWriter.TagStatusWaiting ? 0 : 1
Behavior on opacity { NumberAnimation { duration: 300 } }
property bool shown: nfcHelper.status == NfcHelper.TagStatusWritten || nfcHelper.status == NfcHelper.TagStatusFailed
property bool shown: nfcWriter.status == NfcThingActionWriter.TagStatusWritten || nfcWriter.status == NfcThingActionWriter.TagStatusFailed
BusyIndicator {
anchors.fill: parent
running: visible
visible: nfcHelper.status == NfcHelper.TagStatusWriting
visible: nfcWriter.status == NfcThingActionWriter.TagStatusWriting
}
Item {
@ -176,8 +175,8 @@ Page {
y: (tick.height - height) / 2
height: app.iconSize * 4
width: app.iconSize * 4
name: nfcHelper.status == NfcHelper.TagStatusFailed ? "../images/close.svg" : "../images/tick.svg"
color: nfcHelper.status == NfcHelper.TagStatusFailed ? "red" : "green"
name: nfcWriter.status == NfcThingActionWriter.TagStatusFailed ? "../images/close.svg" : "../images/tick.svg"
color: nfcWriter.status == NfcThingActionWriter.TagStatusFailed ? "red" : "green"
}
}
}
@ -191,12 +190,12 @@ Page {
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: nfcHelper.actions
model: nfcWriter.actions
clip: true
delegate: RuleActionDelegate {
ruleAction: nfcHelper.actions.get(index)
ruleAction: nfcWriter.actions.get(index)
width: parent.width
onRemoveRuleAction: nfcHelper.actions.removeRuleAction(index)
onRemoveRuleAction: nfcWriter.actions.removeRuleAction(index)
}
}
@ -205,11 +204,11 @@ Page {
Layout.fillWidth: true
Layout.margins: app.margins
onClicked: {
var action = nfcHelper.actions.createNewRuleAction()
var action = nfcWriter.actions.createNewRuleAction()
action.thingId = root.thing.id
var page = pageStack.push("SelectRuleActionPage.qml", {ruleAction: action});
page.done.connect(function() {
nfcHelper.actions.addRuleAction(action);
nfcWriter.actions.addRuleAction(action);
pageStack.pop();
})
page.backPressed.connect(function() {