Add support for NFC tag writing and reading
This commit is contained in:
parent
621bfaed91
commit
c36000c1e9
@ -30,6 +30,7 @@ SOURCES += \
|
|||||||
../nymea-app/stylecontroller.cpp \
|
../nymea-app/stylecontroller.cpp \
|
||||||
../nymea-app/platformhelper.cpp \
|
../nymea-app/platformhelper.cpp \
|
||||||
../nymea-app/nfchelper.cpp \
|
../nymea-app/nfchelper.cpp \
|
||||||
|
../nymea-app/nfcthingactionwriter.cpp \
|
||||||
../nymea-app/platformintegration/android/platformhelperandroid.cpp \
|
../nymea-app/platformintegration/android/platformhelperandroid.cpp \
|
||||||
service_main.cpp
|
service_main.cpp
|
||||||
|
|
||||||
@ -40,6 +41,7 @@ HEADERS += \
|
|||||||
../nymea-app/stylecontroller.h \
|
../nymea-app/stylecontroller.h \
|
||||||
../nymea-app/platformhelper.h \
|
../nymea-app/platformhelper.h \
|
||||||
../nymea-app/nfchelper.h \
|
../nymea-app/nfchelper.h \
|
||||||
|
../nymea-app/nfcthingactionwriter.h \
|
||||||
../nymea-app/platformintegration/android/platformhelperandroid.h \
|
../nymea-app/platformintegration/android/platformhelperandroid.h \
|
||||||
|
|
||||||
DISTFILES += \
|
DISTFILES += \
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include "../nymea-app/stylecontroller.h"
|
#include "../nymea-app/stylecontroller.h"
|
||||||
#include "../nymea-app/platformhelper.h"
|
#include "../nymea-app/platformhelper.h"
|
||||||
#include "../nymea-app/nfchelper.h"
|
#include "../nymea-app/nfchelper.h"
|
||||||
|
#include "../nymea-app/nfcthingactionwriter.h"
|
||||||
#include "../nymea-app/platformintegration/android/platformhelperandroid.h"
|
#include "../nymea-app/platformintegration/android/platformhelperandroid.h"
|
||||||
|
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
@ -41,7 +42,8 @@ DeviceControlApplication::DeviceControlApplication(int argc, char *argv[]) : QAp
|
|||||||
registerQmlTypes();
|
registerQmlTypes();
|
||||||
qmlRegisterSingletonType<PlatformHelper>("Nymea", 1, 0, "PlatformHelper", platformHelperProvider);
|
qmlRegisterSingletonType<PlatformHelper>("Nymea", 1, 0, "PlatformHelper", platformHelperProvider);
|
||||||
qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" );
|
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);
|
StyleController *styleController = new StyleController(this);
|
||||||
m_qmlEngine->rootContext()->setContextProperty("styleController", styleController);
|
m_qmlEngine->rootContext()->setContextProperty("styleController", styleController);
|
||||||
@ -168,6 +170,11 @@ void DeviceControlApplication::runNfcAction()
|
|||||||
continue;
|
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);
|
QString actionTypeName = parts.at(0);
|
||||||
ActionType *actionType = thing->thingClass()->actionTypes()->findByName(actionTypeName);
|
ActionType *actionType = thing->thingClass()->actionTypes()->findByName(actionTypeName);
|
||||||
if (!actionType) {
|
if (!actionType) {
|
||||||
@ -188,6 +195,8 @@ void DeviceControlApplication::runNfcAction()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qDebug() << "Parameters in NFC uri:" << paramsInUri;
|
||||||
|
|
||||||
QVariantList params;
|
QVariantList params;
|
||||||
for (int j = 0; j < actionType->paramTypes()->rowCount(); j++) {
|
for (int j = 0; j < actionType->paramTypes()->rowCount(); j++) {
|
||||||
ParamType *paramType = actionType->paramTypes()->get(j);
|
ParamType *paramType = actionType->paramTypes()->get(j);
|
||||||
@ -201,6 +210,8 @@ void DeviceControlApplication::runNfcAction()
|
|||||||
params.append(param);
|
params.append(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qDebug() << "Action parameters:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
|
||||||
|
|
||||||
m_engine->thingManager()->executeAction(thingId, actionType->id(), params);
|
m_engine->thingManager()->executeAction(thingId, actionType->id(), params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
#include "applogcontroller.h"
|
#include "applogcontroller.h"
|
||||||
#include "ruletemplates/messages.h"
|
#include "ruletemplates/messages.h"
|
||||||
#include "nfchelper.h"
|
#include "nfchelper.h"
|
||||||
|
#include "nfcthingactionwriter.h"
|
||||||
|
|
||||||
QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
|
QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
|
||||||
{
|
{
|
||||||
@ -67,7 +68,6 @@ QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -125,7 +125,8 @@ int main(int argc, char *argv[])
|
|||||||
QQmlApplicationEngine *engine = new QQmlApplicationEngine();
|
QQmlApplicationEngine *engine = new QQmlApplicationEngine();
|
||||||
|
|
||||||
qmlRegisterSingletonType<PlatformHelper>("Nymea", 1, 0, "PlatformHelper", platformHelperProvider);
|
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();
|
PushNotifications::instance()->connectClient();
|
||||||
qmlRegisterSingletonType<PushNotifications>("Nymea", 1, 0, "PushNotifications", PushNotifications::pushNotificationsProvider);
|
qmlRegisterSingletonType<PushNotifications>("Nymea", 1, 0, "PushNotifications", PushNotifications::pushNotificationsProvider);
|
||||||
|
|||||||
@ -1,181 +1,28 @@
|
|||||||
#include "nfchelper.h"
|
#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 <QNearFieldManager>
|
||||||
#include <QNdefMessage>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QNdefNfcUriRecord>
|
|
||||||
#include <QUrl>
|
|
||||||
#include <QUrlQuery>
|
|
||||||
|
|
||||||
NfcHelper::NfcHelper(QObject *parent):
|
NfcHelper::NfcHelper(QObject *parent) : QObject(parent)
|
||||||
QObject(parent),
|
|
||||||
m_manager(new QNearFieldManager(this)),
|
|
||||||
m_actions(new RuleActions(this))
|
|
||||||
{
|
{
|
||||||
|
|
||||||
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();
|
static NfcHelper *thiz = nullptr;
|
||||||
}
|
if (!thiz) {
|
||||||
|
thiz = new NfcHelper();
|
||||||
Engine *NfcHelper::engine() const
|
|
||||||
{
|
|
||||||
return m_engine;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NfcHelper::setEngine(Engine *engine)
|
|
||||||
{
|
|
||||||
if (m_engine != engine) {
|
|
||||||
m_engine = engine;
|
|
||||||
emit engineChanged();
|
|
||||||
updateContent();
|
|
||||||
}
|
}
|
||||||
|
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) {
|
QNearFieldManager manager;
|
||||||
m_thing = thing;
|
return manager.isAvailable();
|
||||||
emit thingChanged();
|
|
||||||
updateContent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@ -2,70 +2,22 @@
|
|||||||
#define NFCHELPER_H
|
#define NFCHELPER_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QNearFieldManager>
|
#include <QQmlEngine>
|
||||||
#include <QNdefMessage>
|
|
||||||
|
|
||||||
#include "types/device.h"
|
|
||||||
#include "engine.h"
|
|
||||||
#include "types/ruleactions.h"
|
|
||||||
|
|
||||||
class NfcHelper : public QObject
|
class NfcHelper : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(Engine *engine READ engine WRITE setEngine NOTIFY engineChanged)
|
Q_PROPERTY(bool isAvailable READ isAvailable CONSTANT)
|
||||||
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:
|
public:
|
||||||
enum TagStatus {
|
static NfcHelper* instance();
|
||||||
TagStatusWaiting,
|
static QObject *nfcHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
|
||||||
TagStatusWriting,
|
|
||||||
TagStatusWritten,
|
|
||||||
TagStatusFailed
|
|
||||||
};
|
|
||||||
Q_ENUM(TagStatus)
|
|
||||||
|
|
||||||
explicit NfcHelper(QObject *parent = nullptr);
|
|
||||||
~NfcHelper();
|
|
||||||
|
|
||||||
Engine *engine() const;
|
bool isAvailable() 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:
|
private:
|
||||||
QNearFieldManager *m_manager = nullptr;
|
explicit NfcHelper(QObject *parent = nullptr);
|
||||||
Engine *m_engine = nullptr;
|
|
||||||
Device *m_thing = nullptr;
|
|
||||||
RuleActions* m_actions;
|
|
||||||
|
|
||||||
TagStatus m_status = TagStatusWaiting;
|
|
||||||
|
|
||||||
QNdefMessage m_currentMessage;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // NFCHELPER_H
|
#endif // NFCHELPER_H
|
||||||
|
|||||||
185
nymea-app/nfcthingactionwriter.cpp
Normal file
185
nymea-app/nfcthingactionwriter.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
76
nymea-app/nfcthingactionwriter.h
Normal file
76
nymea-app/nfcthingactionwriter.h
Normal 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
|
||||||
@ -15,6 +15,7 @@ PRE_TARGETDEPS += ../libnymea-app
|
|||||||
HEADERS += \
|
HEADERS += \
|
||||||
mainmenumodel.h \
|
mainmenumodel.h \
|
||||||
nfchelper.h \
|
nfchelper.h \
|
||||||
|
nfcthingactionwriter.h \
|
||||||
platformintegration/generic/raspberrypihelper.h \
|
platformintegration/generic/raspberrypihelper.h \
|
||||||
stylecontroller.h \
|
stylecontroller.h \
|
||||||
pushnotifications.h \
|
pushnotifications.h \
|
||||||
@ -26,6 +27,7 @@ HEADERS += \
|
|||||||
SOURCES += main.cpp \
|
SOURCES += main.cpp \
|
||||||
mainmenumodel.cpp \
|
mainmenumodel.cpp \
|
||||||
nfchelper.cpp \
|
nfchelper.cpp \
|
||||||
|
nfcthingactionwriter.cpp \
|
||||||
platformintegration/generic/raspberrypihelper.cpp \
|
platformintegration/generic/raspberrypihelper.cpp \
|
||||||
stylecontroller.cpp \
|
stylecontroller.cpp \
|
||||||
pushnotifications.cpp \
|
pushnotifications.cpp \
|
||||||
@ -171,3 +173,5 @@ contains(ANDROID_TARGET_ARCH,) {
|
|||||||
arm64-v8a
|
arm64-v8a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ANDROID_ABIS = armeabi-v7a arm64-v8a
|
||||||
|
|
||||||
|
|||||||
@ -112,14 +112,19 @@ Page {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
thingMenu.addItem(menuEntryComponent.createObject(thingMenu,
|
print("*** creating menu")
|
||||||
{
|
print("NFC", NfcHelper.isAvailable)
|
||||||
text: qsTr("Write NFC tag"),
|
if (NfcHelper.isAvailable) {
|
||||||
iconSource: "../images/nfc.svg",
|
thingMenu.addItem(menuEntryComponent.createObject(thingMenu,
|
||||||
functionName: "writeNfcTag"
|
{
|
||||||
|
text: qsTr("Write NFC tag"),
|
||||||
|
iconSource: "../images/nfc.svg",
|
||||||
|
functionName: "writeNfcTag"
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDeviceMagicPage() {
|
function openDeviceMagicPage() {
|
||||||
pageStack.push(Qt.resolvedUrl("../magic/DeviceRulesPage.qml"), {device: root.device})
|
pageStack.push(Qt.resolvedUrl("../magic/DeviceRulesPage.qml"), {device: root.device})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,11 +47,10 @@ Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NfcHelper {
|
NfcThingActionWriter {
|
||||||
id: nfcHelper
|
id: nfcWriter
|
||||||
engine: _engine
|
engine: _engine
|
||||||
thing: root.thing
|
thing: root.thing
|
||||||
|
|
||||||
}
|
}
|
||||||
// nfcHelper.writeThingStates(engine, root.thing)
|
// nfcHelper.writeThingStates(engine, root.thing)
|
||||||
|
|
||||||
@ -66,14 +65,14 @@ Page {
|
|||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: {
|
text: {
|
||||||
switch (nfcHelper.status) {
|
switch (nfcWriter.status) {
|
||||||
case NfcHelper.TagStatusWaiting:
|
case NfcThingActionWriter.TagStatusWaiting:
|
||||||
return qsTr("Tap an NFC tag to link it to %1.").arg(root.thing.name)
|
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...")
|
return qsTr("Writing NFC tag...")
|
||||||
case NfcHelper.TagStatusWritten:
|
case NfcThingActionWriter.TagStatusWritten:
|
||||||
return qsTr("NFC tag linked to %1.").arg(root.thing.name)
|
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)
|
return qsTr("Failed linking the NFC tag to %1.").arg(root.thing.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +82,7 @@ Page {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
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
|
font.pixelSize: app.smallFont
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
enabled: false
|
enabled: false
|
||||||
@ -118,7 +117,7 @@ Page {
|
|||||||
width: app.iconSize * 2
|
width: app.iconSize * 2
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.horizontalCenterOffset: - app.iconSize * 2
|
anchors.horizontalCenterOffset: - app.iconSize * 2
|
||||||
visible: nfcHelper.status == NfcHelper.TagStatusWaiting
|
visible: nfcWriter.status == NfcThingActionWriter.TagStatusWaiting
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@ -128,7 +127,7 @@ Page {
|
|||||||
scale: 1.5
|
scale: 1.5
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.horizontalCenterOffset: app.iconSize * 2
|
anchors.horizontalCenterOffset: app.iconSize * 2
|
||||||
visible: nfcHelper.status == NfcHelper.TagStatusWaiting
|
visible: nfcWriter.status == NfcThingActionWriter.TagStatusWaiting
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@ -154,15 +153,15 @@ Page {
|
|||||||
color: app.backgroundColor
|
color: app.backgroundColor
|
||||||
border.width: 4
|
border.width: 4
|
||||||
border.color: app.foregroundColor
|
border.color: app.foregroundColor
|
||||||
opacity: nfcHelper.status == NfcHelper.TagStatusWaiting ? 0 : 1
|
opacity: nfcWriter.status == NfcThingActionWriter.TagStatusWaiting ? 0 : 1
|
||||||
Behavior on opacity { NumberAnimation { duration: 300 } }
|
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 {
|
BusyIndicator {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
running: visible
|
running: visible
|
||||||
visible: nfcHelper.status == NfcHelper.TagStatusWriting
|
visible: nfcWriter.status == NfcThingActionWriter.TagStatusWriting
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@ -176,8 +175,8 @@ Page {
|
|||||||
y: (tick.height - height) / 2
|
y: (tick.height - height) / 2
|
||||||
height: app.iconSize * 4
|
height: app.iconSize * 4
|
||||||
width: app.iconSize * 4
|
width: app.iconSize * 4
|
||||||
name: nfcHelper.status == NfcHelper.TagStatusFailed ? "../images/close.svg" : "../images/tick.svg"
|
name: nfcWriter.status == NfcThingActionWriter.TagStatusFailed ? "../images/close.svg" : "../images/tick.svg"
|
||||||
color: nfcHelper.status == NfcHelper.TagStatusFailed ? "red" : "green"
|
color: nfcWriter.status == NfcThingActionWriter.TagStatusFailed ? "red" : "green"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,12 +190,12 @@ Page {
|
|||||||
ListView {
|
ListView {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
model: nfcHelper.actions
|
model: nfcWriter.actions
|
||||||
clip: true
|
clip: true
|
||||||
delegate: RuleActionDelegate {
|
delegate: RuleActionDelegate {
|
||||||
ruleAction: nfcHelper.actions.get(index)
|
ruleAction: nfcWriter.actions.get(index)
|
||||||
width: parent.width
|
width: parent.width
|
||||||
onRemoveRuleAction: nfcHelper.actions.removeRuleAction(index)
|
onRemoveRuleAction: nfcWriter.actions.removeRuleAction(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,11 +204,11 @@ Page {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.margins: app.margins
|
Layout.margins: app.margins
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var action = nfcHelper.actions.createNewRuleAction()
|
var action = nfcWriter.actions.createNewRuleAction()
|
||||||
action.thingId = root.thing.id
|
action.thingId = root.thing.id
|
||||||
var page = pageStack.push("SelectRuleActionPage.qml", {ruleAction: action});
|
var page = pageStack.push("SelectRuleActionPage.qml", {ruleAction: action});
|
||||||
page.done.connect(function() {
|
page.done.connect(function() {
|
||||||
nfcHelper.actions.addRuleAction(action);
|
nfcWriter.actions.addRuleAction(action);
|
||||||
pageStack.pop();
|
pageStack.pop();
|
||||||
})
|
})
|
||||||
page.backPressed.connect(function() {
|
page.backPressed.connect(function() {
|
||||||
|
|||||||
Reference in New Issue
Block a user