Merge PR #451: Add support for writing NFC tags and launching stuff with them
This commit is contained in:
commit
ed38dd2c82
@ -2,7 +2,7 @@ TEMPLATE = lib
|
||||
TARGET = service
|
||||
CONFIG += dll
|
||||
QT += core androidextras
|
||||
QT += network qml quick quickcontrols2 svg websockets bluetooth charts
|
||||
QT += network qml quick quickcontrols2 svg websockets bluetooth charts nfc
|
||||
|
||||
include(../config.pri)
|
||||
include(../android_openssl/openssl.pri)
|
||||
@ -29,6 +29,8 @@ SOURCES += \
|
||||
nymeaappservice/androidbinder.cpp \
|
||||
../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
|
||||
|
||||
@ -38,6 +40,8 @@ HEADERS += \
|
||||
nymeaappservice/androidbinder.h \
|
||||
../nymea-app/stylecontroller.h \
|
||||
../nymea-app/platformhelper.h \
|
||||
../nymea-app/nfchelper.h \
|
||||
../nymea-app/nfcthingactionwriter.h \
|
||||
../nymea-app/platformintegration/android/platformhelperandroid.h \
|
||||
|
||||
DISTFILES += \
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
#include "libnymea-app-core.h"
|
||||
#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>
|
||||
@ -13,6 +15,8 @@
|
||||
#include <QtQml>
|
||||
#include <QtAndroid>
|
||||
#include <QAndroidJniObject>
|
||||
#include <QAndroidIntent>
|
||||
#include <QNdefNfcUriRecord>
|
||||
|
||||
QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
|
||||
{
|
||||
@ -26,41 +30,190 @@ DeviceControlApplication::DeviceControlApplication(int argc, char *argv[]) : QAp
|
||||
setApplicationName("nymea-app");
|
||||
setOrganizationName("nymea");
|
||||
|
||||
QString nymeaId = QtAndroid::androidActivity().callObjectMethod<jstring>("nymeaId").toString();
|
||||
QString thingId = QtAndroid::androidActivity().callObjectMethod<jstring>("thingId").toString();
|
||||
|
||||
QSettings settings;
|
||||
|
||||
NymeaDiscovery *discovery = new NymeaDiscovery(this);
|
||||
m_discovery = new NymeaDiscovery(this);
|
||||
AWSClient::instance()->setConfig(settings.value("cloudEnvironment").toString());
|
||||
discovery->setAwsClient(AWSClient::instance());
|
||||
NymeaHost *host = discovery->nymeaHosts()->find(nymeaId);
|
||||
m_discovery->setAwsClient(AWSClient::instance());
|
||||
|
||||
m_engine = new Engine(this);
|
||||
|
||||
m_qmlEngine = new QQmlApplicationEngine(this);
|
||||
registerQmlTypes();
|
||||
qmlRegisterSingletonType<PlatformHelper>("Nymea", 1, 0, "PlatformHelper", platformHelperProvider);
|
||||
qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" );
|
||||
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);
|
||||
m_qmlEngine->rootContext()->setContextProperty("engine", m_engine);
|
||||
m_qmlEngine->rootContext()->setContextProperty("_engine", m_engine);
|
||||
m_qmlEngine->rootContext()->setContextProperty("controlledThingId", ""); // Unknown at this point
|
||||
|
||||
m_qmlEngine->load(QUrl(QLatin1String("qrc:/Main.qml")));
|
||||
|
||||
jboolean startedByNfc = QtAndroid::androidActivity().callMethod<jboolean>("startedByNfc", "()Z");
|
||||
if (startedByNfc) {
|
||||
qDebug() << "**** Started by NFC";
|
||||
qDebug() << "Registering NFC handler and waiting for message.";
|
||||
|
||||
QNearFieldManager *manager = new QNearFieldManager(this);
|
||||
manager->registerNdefMessageHandler(this, SLOT(handleNdefMessage(QNdefMessage,QNearFieldTarget*)));
|
||||
|
||||
} else {
|
||||
qDebug() << "*** Started by other intent";
|
||||
qDebug() << "Expecing nymeaId and thingId in intent extras.";
|
||||
QString nymeaId = QtAndroid::androidActivity().callObjectMethod<jstring>("nymeaId").toString();
|
||||
QString thingId = QtAndroid::androidActivity().callObjectMethod<jstring>("thingId").toString();
|
||||
|
||||
connectToNymea(nymeaId);
|
||||
m_qmlEngine->rootContext()->setContextProperty("controlledThingId", thingId);
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceControlApplication::handleNdefMessage(QNdefMessage message, QNearFieldTarget *target)
|
||||
{
|
||||
qDebug() << "************* NFC message!" << message.toByteArray();
|
||||
if (message.count() < 1) {
|
||||
qWarning() << "NFC message doesn't contain any records...";
|
||||
return;
|
||||
}
|
||||
// NOTE: At this point we're only supporting one NDEF record per message
|
||||
QNdefRecord record = message.first();
|
||||
QNdefNfcUriRecord uriRecord(record);
|
||||
|
||||
QUrl url = uriRecord.uri();
|
||||
if (url.scheme() != "nymea") {
|
||||
qWarning() << "NDEF URI record scheme is not \"nymea://\"";
|
||||
return;
|
||||
}
|
||||
|
||||
QUuid nymeaId = QUuid(url.host());
|
||||
if (nymeaId.isNull()) {
|
||||
qWarning() << "Invalid nymea UUID in NDEF record.";
|
||||
return;
|
||||
}
|
||||
|
||||
QUuid thingId = QUuid(QUrlQuery(url).queryItemValue("t"));
|
||||
if (thingId.isNull()) {
|
||||
qWarning() << "Invalid thing in NDEF record";
|
||||
return;
|
||||
}
|
||||
|
||||
m_pendingNfcAction = url;
|
||||
|
||||
connectToNymea(nymeaId);
|
||||
m_qmlEngine->rootContext()->setContextProperty("controlledThingId", thingId);
|
||||
|
||||
connect(m_engine->thingManager(), &DeviceManager::fetchingDataChanged, [this](){
|
||||
if (m_engine->jsonRpcClient()->connected() && !m_engine->thingManager()->fetchingData()) {
|
||||
qDebug() << "Ready to process commands";
|
||||
runNfcAction();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void DeviceControlApplication::connectToNymea(const QUuid &nymeaId)
|
||||
{
|
||||
NymeaHost *host = m_discovery->nymeaHosts()->find(nymeaId);
|
||||
if (!host) {
|
||||
qWarning() << "No such nymea host:" << nymeaId;
|
||||
// TODO: We could wait here until the discovery finds it... But it really should be cached already...
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Engine *m_engine = new Engine(this);
|
||||
|
||||
qDebug() << "Connecting to:" << host;
|
||||
qDebug() << "Connecting to:" << host->name();
|
||||
m_engine->jsonRpcClient()->connectToHost(host);
|
||||
|
||||
qDebug() << "Creating QML view";
|
||||
QQmlApplicationEngine *qmlEngine = new QQmlApplicationEngine(this);
|
||||
|
||||
registerQmlTypes();
|
||||
|
||||
qmlRegisterSingletonType<PlatformHelper>("Nymea", 1, 0, "PlatformHelper", platformHelperProvider);
|
||||
qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" );
|
||||
|
||||
StyleController styleController;
|
||||
qmlEngine->rootContext()->setContextProperty("styleController", &styleController);
|
||||
qmlEngine->rootContext()->setContextProperty("engine", m_engine);
|
||||
qmlEngine->rootContext()->setContextProperty("_engine", m_engine);
|
||||
qmlEngine->rootContext()->setContextProperty("controlledThingId", thingId);
|
||||
|
||||
qmlEngine->load(QUrl(QLatin1String("qrc:/Main.qml")));
|
||||
}
|
||||
|
||||
void DeviceControlApplication::runNfcAction()
|
||||
{
|
||||
if (!m_pendingNfcAction.isEmpty()) {
|
||||
qDebug() << "NFC action:" << m_pendingNfcAction;
|
||||
}
|
||||
QUrl url = m_pendingNfcAction;
|
||||
m_pendingNfcAction.clear();
|
||||
|
||||
if (url.scheme() != "nymea") {
|
||||
qWarning() << "NDEF URI record scheme is not \"nymea://\" in" << url.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
QUuid nymeaId = QUuid(url.host());
|
||||
if (nymeaId.isNull()) {
|
||||
qWarning() << "Invalid nymea UUID" << url.host() << "in NDEF record" << url.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
QUuid thingId = QUuid(QUrlQuery(url).queryItemValue("t"));
|
||||
Device *thing = m_engine->thingManager()->things()->getThing(thingId);
|
||||
if (!thing) {
|
||||
qDebug() << "Thing" << thingId.toString() << "from" << url.toString() << "doesn't exist on nymea host" << nymeaId.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
QList<QPair<QString, QString>> queryItems = QUrlQuery(url.query()).queryItems();
|
||||
for (int i = 0; i < queryItems.count(); i++) {
|
||||
QString entryName = queryItems.at(i).first;
|
||||
if (entryName == "t") {
|
||||
continue;
|
||||
}
|
||||
if (!entryName.startsWith("a")) {
|
||||
qDebug() << "Only actions are supported. Skipping query item" << entryName;
|
||||
continue;
|
||||
}
|
||||
|
||||
QString actionString = queryItems.at(i).second;
|
||||
QStringList parts = actionString.split("#");
|
||||
if (parts.count() == 0) {
|
||||
qDebug() << "Invalid action definition:" << actionString;
|
||||
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) {
|
||||
qWarning() << "Invalid action name" << actionType << "in url:" << url.toString();
|
||||
continue;
|
||||
}
|
||||
|
||||
QHash<QString, QVariant> paramsInUri;
|
||||
if (parts.count() > 1) {
|
||||
QString paramsString = parts.at(1);
|
||||
foreach (const QString ¶mString, paramsString.split("+")) {
|
||||
QStringList parts = paramString.split(":");
|
||||
if (parts.count() != 2) {
|
||||
qWarning() << "Invalid param format" << paramString << "in url:" << url.toString();
|
||||
continue;
|
||||
}
|
||||
paramsInUri.insert(parts.at(0), parts.at(1));
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Parameters in NFC uri:" << paramsInUri;
|
||||
|
||||
QVariantList params;
|
||||
for (int j = 0; j < actionType->paramTypes()->rowCount(); j++) {
|
||||
ParamType *paramType = actionType->paramTypes()->get(j);
|
||||
QVariantMap param;
|
||||
param.insert("paramTypeId", paramType->id());
|
||||
if (paramsInUri.contains(paramType->name())) {
|
||||
param.insert("value", paramsInUri.value(paramType->name()));
|
||||
} else {
|
||||
param.insert("value", paramType->defaultValue());
|
||||
}
|
||||
params.append(param);
|
||||
}
|
||||
|
||||
qDebug() << "Action parameters:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson());
|
||||
|
||||
m_engine->thingManager()->executeAction(thingId, actionType->id(), params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -2,6 +2,14 @@
|
||||
#define DEVICECONTROLAPPLICATION_H
|
||||
|
||||
#include <QApplication>
|
||||
#include <QNearFieldManager>
|
||||
#include <QNdefMessage>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QNdefMessage>
|
||||
|
||||
#include "types/ruleactions.h"
|
||||
#include "connection/discovery/nymeadiscovery.h"
|
||||
#include "engine.h"
|
||||
|
||||
class DeviceControlApplication : public QApplication
|
||||
{
|
||||
@ -9,6 +17,21 @@ class DeviceControlApplication : public QApplication
|
||||
public:
|
||||
explicit DeviceControlApplication(int argc, char *argv[]);
|
||||
|
||||
private slots:
|
||||
void handleNdefMessage(QNdefMessage message,QNearFieldTarget* target);
|
||||
|
||||
void connectToNymea(const QUuid &nymeaId);
|
||||
|
||||
void runNfcAction();
|
||||
|
||||
private:
|
||||
NymeaDiscovery *m_discovery = nullptr;
|
||||
Engine *m_engine = nullptr;
|
||||
QQmlApplicationEngine *m_qmlEngine = nullptr;
|
||||
|
||||
QUrl m_pendingNfcAction;
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // DEVICECONTROLAPPLICATION_H
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
#define NYMEAAPPSERVICE_H
|
||||
|
||||
#include <QAndroidService>
|
||||
#include <QNearFieldManager>
|
||||
#include <QNdefMessage>
|
||||
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
@ -49,11 +49,12 @@ ActionType *ActionTypes::get(int index) const
|
||||
ActionType *ActionTypes::getActionType(const QUuid &actionTypeId) const
|
||||
{
|
||||
foreach (ActionType *actionType, m_actionTypes) {
|
||||
qDebug() << "checking:" << actionType->id();
|
||||
if (actionType->id() == actionTypeId) {
|
||||
return actionType;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ActionTypes::rowCount(const QModelIndex &parent) const
|
||||
|
||||
@ -236,5 +236,7 @@
|
||||
<file>ui/images/connections/nm-signal-100-secure.svg</file>
|
||||
<file>ui/images/connections/bluetooth.svg</file>
|
||||
<file>ui/images/connections/network-wired-disabled.svg</file>
|
||||
<file>ui/images/nfc.svg</file>
|
||||
<file>ui/images/smartphone.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -52,6 +52,8 @@
|
||||
#include "pushnotifications.h"
|
||||
#include "applogcontroller.h"
|
||||
#include "ruletemplates/messages.h"
|
||||
#include "nfchelper.h"
|
||||
#include "nfcthingactionwriter.h"
|
||||
|
||||
QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
|
||||
{
|
||||
@ -66,7 +68,6 @@ QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
@ -124,6 +125,8 @@ int main(int argc, char *argv[])
|
||||
QQmlApplicationEngine *engine = new QQmlApplicationEngine();
|
||||
|
||||
qmlRegisterSingletonType<PlatformHelper>("Nymea", 1, 0, "PlatformHelper", platformHelperProvider);
|
||||
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);
|
||||
|
||||
28
nymea-app/nfchelper.cpp
Normal file
28
nymea-app/nfchelper.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include "nfchelper.h"
|
||||
|
||||
#include <QNearFieldManager>
|
||||
|
||||
NfcHelper::NfcHelper(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
NfcHelper *NfcHelper::instance()
|
||||
{
|
||||
static NfcHelper *thiz = nullptr;
|
||||
if (!thiz) {
|
||||
thiz = new NfcHelper();
|
||||
}
|
||||
return thiz;
|
||||
}
|
||||
|
||||
QObject *NfcHelper::nfcHelperProvider(QQmlEngine */*engine*/, QJSEngine */*scriptEngine*/)
|
||||
{
|
||||
return instance();
|
||||
}
|
||||
|
||||
bool NfcHelper::isAvailable() const
|
||||
{
|
||||
QNearFieldManager manager;
|
||||
return manager.isAvailable();
|
||||
}
|
||||
23
nymea-app/nfchelper.h
Normal file
23
nymea-app/nfchelper.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef NFCHELPER_H
|
||||
#define NFCHELPER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
class NfcHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool isAvailable READ isAvailable CONSTANT)
|
||||
|
||||
public:
|
||||
static NfcHelper* instance();
|
||||
static QObject *nfcHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine);
|
||||
|
||||
|
||||
bool isAvailable() const;
|
||||
|
||||
private:
|
||||
explicit NfcHelper(QObject *parent = nullptr);
|
||||
};
|
||||
|
||||
#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
|
||||
@ -2,7 +2,7 @@ TEMPLATE=app
|
||||
TARGET=nymea-app
|
||||
include(../config.pri)
|
||||
|
||||
QT += network qml quick quickcontrols2 svg websockets bluetooth charts gui-private
|
||||
QT += network qml quick quickcontrols2 svg websockets bluetooth charts gui-private nfc
|
||||
|
||||
INCLUDEPATH += $$top_srcdir/libnymea-app
|
||||
LIBS += -L$$top_builddir/libnymea-app/ -lnymea-app
|
||||
@ -14,6 +14,8 @@ PRE_TARGETDEPS += ../libnymea-app
|
||||
|
||||
HEADERS += \
|
||||
mainmenumodel.h \
|
||||
nfchelper.h \
|
||||
nfcthingactionwriter.h \
|
||||
platformintegration/generic/raspberrypihelper.h \
|
||||
stylecontroller.h \
|
||||
pushnotifications.h \
|
||||
@ -24,6 +26,8 @@ HEADERS += \
|
||||
|
||||
SOURCES += main.cpp \
|
||||
mainmenumodel.cpp \
|
||||
nfchelper.cpp \
|
||||
nfcthingactionwriter.cpp \
|
||||
platformintegration/generic/raspberrypihelper.cpp \
|
||||
stylecontroller.cpp \
|
||||
pushnotifications.cpp \
|
||||
@ -162,3 +166,12 @@ BR=$$BRANDING
|
||||
|
||||
target.path = /usr/bin
|
||||
INSTALLS += target
|
||||
|
||||
contains(ANDROID_TARGET_ARCH,) {
|
||||
ANDROID_ABIS = \
|
||||
armeabi-v7a \
|
||||
arm64-v8a
|
||||
}
|
||||
|
||||
ANDROID_ABIS = armeabi-v7a arm64-v8a
|
||||
|
||||
|
||||
@ -223,5 +223,6 @@
|
||||
<file>ui/components/BatteryStatusIcon.qml</file>
|
||||
<file>ui/components/SetupStatusIcon.qml</file>
|
||||
<file>ui/components/UpdateStatusIcon.qml</file>
|
||||
<file>ui/magic/WriteNfcTagPage.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -96,7 +96,7 @@ Page {
|
||||
thingMenu.addItem(menuEntryComponent.createObject(thingMenu, {text: qsTr("Logs"), iconSource: "../images/logs.svg", functionName: "openDeviceLogPage"}))
|
||||
}
|
||||
|
||||
if (engine.jsonRpcClient.ensureServerVersion(1.6)) {
|
||||
if (engine.jsonRpcClient.ensureServerVersion("1.6")) {
|
||||
thingMenu.addItem(menuEntryComponent.createObject(thingMenu,
|
||||
{
|
||||
text: Qt.binding(function() { return favoritesProxy.count === 0 ? qsTr("Mark as favorite") : qsTr("Remove from favorites")}),
|
||||
@ -111,7 +111,20 @@ Page {
|
||||
functionName: "addToGroup"
|
||||
}))
|
||||
}
|
||||
|
||||
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})
|
||||
}
|
||||
@ -138,6 +151,10 @@ Page {
|
||||
pageStack.push(Qt.resolvedUrl("DeviceLogPage.qml"), {device: root.device });
|
||||
}
|
||||
|
||||
function writeNfcTag() {
|
||||
pageStack.push(Qt.resolvedUrl("../magic/WriteNfcTagPage.qml"), {thing: root.thing})
|
||||
}
|
||||
|
||||
Component {
|
||||
id: menuEntryComponent
|
||||
IconMenuItem {
|
||||
|
||||
94
nymea-app/ui/images/nfc.svg
Normal file
94
nymea-app/ui/images/nfc.svg
Normal file
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg4874"
|
||||
height="96"
|
||||
viewBox="0 0 96 96.000001"
|
||||
width="96"
|
||||
version="1.1"
|
||||
sodipodi:docname="nfc.svg"
|
||||
inkscape:version="1.0.1 (1.0.1+r73)">
|
||||
<path
|
||||
id="path884"
|
||||
style="color:#000000;fill:none"
|
||||
d="M 30.00026,1.073013 V -94.92699 h 96 V 1.073013 Z" />
|
||||
<path
|
||||
id="path886"
|
||||
style="color:#000000;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#808080;fill-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
|
||||
d="m 42.041184,59.03214 19.92185,19.147163 c 2.283959,1.18636 3.037486,-5.033283 -0.0035,-9.561123 L 42.236652,49.66296 M 68.001221,84.147015 V 20.14502 c 0,-2.63041 -0.23782,-4.7121 -0.89648,-6.4668 -0.65866,-1.75461 -1.85,-3.15961 -3.3555,-3.9902 -3.011,-1.6613 -6.6918,-1.4848 -11.725,-1.543 h -0.01172 l -0.03472,4.0001 h 0.02344 c 5.0383,0.0588 6.3519,0.23688 7.8164,1.0449 0.73364,0.40478 1.1508,0.85491 1.541,1.8945 0.39025,1.0396 0.563732,2.692312 0.64258,5.0606 v 58.072038 z"
|
||||
sodipodi:nodetypes="cccccsccccccscscc" />
|
||||
<defs
|
||||
id="defs9" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1380"
|
||||
inkscape:window-height="873"
|
||||
id="namedview7"
|
||||
showgrid="true"
|
||||
inkscape:zoom="2.4135194"
|
||||
inkscape:cx="42.404861"
|
||||
inkscape:cy="39.439781"
|
||||
inkscape:window-x="60"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4874">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid834" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4879">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(67.856999,-78.504993)">
|
||||
<rect
|
||||
id="rect4782"
|
||||
style="color:#000000;fill:none"
|
||||
transform="rotate(90)"
|
||||
height="96"
|
||||
width="96"
|
||||
y="-28.143"
|
||||
x="78.504997" />
|
||||
<path
|
||||
id="path4643"
|
||||
style="color:#000000;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#808080;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
|
||||
d="m -43.869,86.504 -0.01172,0.002 c -5.0328,0.05818 -8.7136,-0.12027 -11.725,1.541 -1.5055,0.83064 -2.6968,2.2356 -3.3555,3.9902 -0.65866,1.7547 -0.89648,3.8364 -0.89648,6.4668 v 56.002 c 0,2.6304 0.23782,4.7121 0.89648,6.4668 0.65866,1.7546 1.85,3.1596 3.3555,3.9902 3.011,1.6613 6.6918,1.4848 11.725,1.543 H -43.869 4.154 4.165719 c 5.0328,-0.0582 8.7136,0.11832 11.725,-1.543 1.5055,-0.83064 2.6968,-2.2356 3.3555,-3.9902 0.65866,-1.7547 0.89648,-3.8364 0.89648,-6.4668 V 98.504 c 0,-2.6304 -0.23782,-4.7121 -0.89648,-6.4668 -0.66,-1.759 -1.851,-3.163 -3.356,-3.994 -3.011,-1.661 -6.6922,-1.483 -11.725,-1.541 L 4.1535,86.5002 h -48.023 z m 0.01172,4 h 48 c 5.0383,0.05877 8.3519,0.23688 9.8164,1.0449 0.73364,0.40478 1.1527,0.85491 1.543,1.8945 0.39025,1.0396 0.64062,2.691 0.64062,5.0605 v 56.002 c 0,2.3696 -0.25037,4.0209 -0.64062,5.0606 -0.39025,1.0396 -0.80933,1.4898 -1.543,1.8945 -1.4645,0.80804 -4.7782,0.98616 -9.8164,1.0449 h -47.977 -0.02344 c -5.0383,-0.0588 -8.3519,-0.23688 -9.8164,-1.0449 -0.73364,-0.40478 -1.1508,-0.85491 -1.541,-1.8945 -0.39025,-1.0396 -0.64258,-2.691 -0.64258,-5.0606 v -56.002 c 0,-2.3696 0.25232,-4.0209 0.64258,-5.0605 0.39025,-1.0396 0.80738,-1.4898 1.541,-1.8945 1.4645,-0.80804 4.7782,-0.98616 9.8164,-1.0449 z" />
|
||||
</g>
|
||||
<g
|
||||
id="g868"
|
||||
transform="translate(87.856999,-78.504993)">
|
||||
<path
|
||||
id="rect864"
|
||||
style="color:#000000;fill:none"
|
||||
transform="rotate(90)"
|
||||
d="M 78.504997,-28.143 H 174.505 v 96 H 78.504997 Z" />
|
||||
</g>
|
||||
<path
|
||||
id="path892"
|
||||
style="color:#000000;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#808080;fill-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
|
||||
d="M 54.001221,37.259895 34.079371,18.112732 c -2.283959,-1.18636 -3.037486,5.033283 0.0035,9.561123 l 19.899987,18.95522 M 28.041184,12.14502 v 64.001995 c 0,2.63041 0.23782,4.7121 0.89648,6.4668 0.65866,1.75461 1.85,3.15961 3.3555,3.9902 3.011,1.6613 6.6918,1.4848 11.725,1.543 h 0.01172 l 0.03472,-4.0001 h -0.02344 c -5.0383,-0.0588 -6.3519,-0.23688 -7.8164,-1.0449 -0.73364,-0.40478 -1.1508,-0.85491 -1.541,-1.8945 -0.39025,-1.0396 -0.563732,-2.692312 -0.64258,-5.0606 V 18.074877 Z"
|
||||
sodipodi:nodetypes="cccccsccccccscscc" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
166
nymea-app/ui/images/smartphone.svg
Normal file
166
nymea-app/ui/images/smartphone.svg
Normal file
@ -0,0 +1,166 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="96"
|
||||
height="96"
|
||||
id="svg4874"
|
||||
version="1.1"
|
||||
inkscape:version="0.91+devel r"
|
||||
viewBox="0 0 96 96.000001"
|
||||
sodipodi:docname="phone-smartphone-symbolic.svg">
|
||||
<defs
|
||||
id="defs4876" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8.7812488"
|
||||
inkscape:cx="15.954447"
|
||||
inkscape:cy="64.905333"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="g4780"
|
||||
showgrid="true"
|
||||
showborder="true"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:snap-center="true"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:snap-global="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid5451"
|
||||
empspacing="8" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="8,-8.0000001"
|
||||
id="guide4063" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="4,-8.0000001"
|
||||
id="guide4065" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,88.000001"
|
||||
id="guide4067" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,92.000001"
|
||||
id="guide4069" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="104,4"
|
||||
id="guide4071" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-5,8.0000001"
|
||||
id="guide4073" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="88,-8.0000001"
|
||||
id="guide4077" />
|
||||
<sodipodi:guide
|
||||
orientation="0,1"
|
||||
position="-8,84.000001"
|
||||
id="guide4074" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="12,-8.0000001"
|
||||
id="guide4076" />
|
||||
<sodipodi:guide
|
||||
orientation="1,0"
|
||||
position="84,-8.0000001"
|
||||
id="guide4080" />
|
||||
<sodipodi:guide
|
||||
position="48,-8.0000001"
|
||||
orientation="1,0"
|
||||
id="guide4170" />
|
||||
<sodipodi:guide
|
||||
position="-8,48"
|
||||
orientation="0,1"
|
||||
id="guide4172" />
|
||||
<sodipodi:guide
|
||||
position="92,-8.0000001"
|
||||
orientation="1,0"
|
||||
id="guide4760" />
|
||||
<sodipodi:guide
|
||||
position="108,12"
|
||||
orientation="0,1"
|
||||
id="guide4182" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4879">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(67.857146,-78.50504)">
|
||||
<g
|
||||
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
|
||||
id="g4845"
|
||||
style="display:inline">
|
||||
<g
|
||||
inkscape:export-ydpi="90"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-filename="next01.png"
|
||||
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
|
||||
id="g4778"
|
||||
inkscape:label="Layer 1">
|
||||
<g
|
||||
transform="matrix(-1,0,0,1,575.99999,611)"
|
||||
id="g4780"
|
||||
style="display:inline">
|
||||
<rect
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
|
||||
id="rect4782"
|
||||
width="96.037987"
|
||||
height="96"
|
||||
x="-438.00244"
|
||||
y="345.36221"
|
||||
transform="scale(-1,1)" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.00000048;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 35.976562,2.0000001 C 30.943803,2.0581871 27.26124,1.881546 24.25,3.5429689 22.74438,4.3736802 21.553141,5.7767541 20.894531,7.5312501 20.235931,9.2857461 19.998047,11.369641 19.998047,14 v 68 c 0,2.630359 0.237884,4.714254 0.896484,6.46875 0.65861,1.754496 1.849849,3.15757 3.355469,3.988281 3.01124,1.661433 6.693803,1.484792 11.726562,1.542969 h 0.01172 24.021485 0.01172 c 5.03276,-0.05818 8.715323,0.118464 11.726563,-1.542969 1.50562,-0.830711 2.696869,-2.233785 3.355469,-3.988281 C 75.762116,86.714254 76,84.630359 76,82 V 14 C 76,11.369641 75.762116,9.2857461 75.103516,7.5312501 74.444916,5.7767541 73.253667,4.3736802 71.748047,3.5429689 68.736807,1.881546 65.054244,2.0581771 60.021484,2.0000001 H 60.009766 35.988281 Z M 24.080078,12 h 47.839844 c 0.04599,0.606222 0.07813,1.255513 0.07813,2 V 82 H 24 V 14 c 0,-0.744487 0.03408,-1.393778 0.08008,-2 z M 40,86 h 16 v 3.998047 H 40 Z"
|
||||
transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
|
||||
id="path4297"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cscsscccccccsssssccccccsccscccccc" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
222
nymea-app/ui/magic/WriteNfcTagPage.qml
Normal file
222
nymea-app/ui/magic/WriteNfcTagPage.qml
Normal file
@ -0,0 +1,222 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* For any further details and any questions please contact us under
|
||||
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||
* https://nymea.io/license/faq
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import "../components"
|
||||
|
||||
Page {
|
||||
id: root
|
||||
property Thing thing: null
|
||||
readonly property ThingClass thingClass: thing.thingClass
|
||||
|
||||
header: NymeaHeader {
|
||||
text: qsTr("Write NFC tag")
|
||||
onBackPressed: {
|
||||
pageStack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NfcThingActionWriter {
|
||||
id: nfcWriter
|
||||
engine: _engine
|
||||
thing: root.thing
|
||||
}
|
||||
// nfcHelper.writeThingStates(engine, root.thing)
|
||||
|
||||
GridLayout {
|
||||
anchors.fill: parent
|
||||
columns: app.landscape ? 2 : 1
|
||||
|
||||
ColumnLayout {
|
||||
Layout.preferredWidth: parent.width / parent.columns
|
||||
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: {
|
||||
switch (nfcWriter.status) {
|
||||
case NfcThingActionWriter.TagStatusWaiting:
|
||||
return qsTr("Tap an NFC tag to link it to %1.").arg(root.thing.name)
|
||||
case NfcThingActionWriter.TagStatusWriting:
|
||||
return qsTr("Writing NFC tag...")
|
||||
case NfcThingActionWriter.TagStatusWritten:
|
||||
return qsTr("NFC tag linked to %1.").arg(root.thing.name)
|
||||
case NfcThingActionWriter.TagStatusFailed:
|
||||
return qsTr("Failed linking the NFC tag to %1.").arg(root.thing.name)
|
||||
}
|
||||
}
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Required tag size: %1 bytes").arg(nfcWriter.messageSize)
|
||||
font.pixelSize: app.smallFont
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
enabled: false
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: app.iconSize * 8
|
||||
|
||||
SequentialAnimation {
|
||||
loops: Animation.Infinite
|
||||
running: true
|
||||
|
||||
PropertyAction { target: phoneIcon; property: "anchors.horizontalCenterOffset"; value: app.iconSize * 2 }
|
||||
PropertyAction { target: phoneIcon; property: "scale"; value: 1.3 }
|
||||
NumberAnimation { target: phoneIcon; property: "opacity"; duration: 500; to: 1 }
|
||||
PauseAnimation { duration: 500 }
|
||||
ParallelAnimation {
|
||||
NumberAnimation { target: phoneIcon; property: "anchors.horizontalCenterOffset"; from: app.iconSize * 2; to: -app.iconSize * 2; duration: 1500; easing.type: Easing.OutQuad }
|
||||
NumberAnimation { target: phoneIcon; property: "scale"; duration: 1500; from: 1.3; to: 1; easing.type: Easing.InOutQuad }
|
||||
}
|
||||
PauseAnimation { duration: 500 }
|
||||
NumberAnimation { target: phoneIcon; property: "opacity"; duration: 500; to: 0 }
|
||||
PauseAnimation { duration: 500 }
|
||||
}
|
||||
|
||||
|
||||
ColorIcon {
|
||||
id: nfcIcon
|
||||
name: "../images/nfc.svg"
|
||||
height: app.iconSize * 2
|
||||
width: app.iconSize * 2
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: - app.iconSize * 2
|
||||
visible: nfcWriter.status == NfcThingActionWriter.TagStatusWaiting
|
||||
}
|
||||
|
||||
Item {
|
||||
id: phoneIcon
|
||||
height: app.iconSize * 5
|
||||
width: app.iconSize * 5
|
||||
scale: 1.5
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: app.iconSize * 2
|
||||
visible: nfcWriter.status == NfcThingActionWriter.TagStatusWaiting
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: phoneIcon.width * .21
|
||||
anchors.rightMargin: phoneIcon.width * .21
|
||||
anchors.topMargin: phoneIcon.height * .1
|
||||
anchors.bottomMargin: phoneIcon.height * .1
|
||||
color: app.backgroundColor
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
name: "../images/smartphone.svg"
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: tick
|
||||
anchors.centerIn: parent
|
||||
height: app.iconSize * 6
|
||||
width: app.iconSize * 6
|
||||
radius: width / 2
|
||||
color: app.backgroundColor
|
||||
border.width: 4
|
||||
border.color: app.foregroundColor
|
||||
opacity: nfcWriter.status == NfcThingActionWriter.TagStatusWaiting ? 0 : 1
|
||||
Behavior on opacity { NumberAnimation { duration: 300 } }
|
||||
|
||||
property bool shown: nfcWriter.status == NfcThingActionWriter.TagStatusWritten || nfcWriter.status == NfcThingActionWriter.TagStatusFailed
|
||||
|
||||
BusyIndicator {
|
||||
anchors.fill: parent
|
||||
running: visible
|
||||
visible: nfcWriter.status == NfcThingActionWriter.TagStatusWriting
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: tick.shown ? 0 : parent.width
|
||||
Behavior on anchors.rightMargin { NumberAnimation { duration: 500 } }
|
||||
clip: true
|
||||
|
||||
ColorIcon {
|
||||
x: (tick.width - width) / 2
|
||||
y: (tick.height - height) / 2
|
||||
height: app.iconSize * 4
|
||||
width: app.iconSize * 4
|
||||
name: nfcWriter.status == NfcThingActionWriter.TagStatusFailed ? "../images/close.svg" : "../images/tick.svg"
|
||||
color: nfcWriter.status == NfcThingActionWriter.TagStatusFailed ? "red" : "green"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
Layout.preferredWidth: parent.width / parent.columns
|
||||
|
||||
ListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: nfcWriter.actions
|
||||
clip: true
|
||||
delegate: RuleActionDelegate {
|
||||
ruleAction: nfcWriter.actions.get(index)
|
||||
width: parent.width
|
||||
onRemoveRuleAction: nfcWriter.actions.removeRuleAction(index)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Add action")
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: app.margins
|
||||
onClicked: {
|
||||
var action = nfcWriter.actions.createNewRuleAction()
|
||||
action.thingId = root.thing.id
|
||||
var page = pageStack.push("SelectRuleActionPage.qml", {ruleAction: action});
|
||||
page.done.connect(function() {
|
||||
nfcWriter.actions.addRuleAction(action);
|
||||
pageStack.pop();
|
||||
})
|
||||
page.backPressed.connect(function() {
|
||||
action.destroy()
|
||||
pageStack.pop();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -64,6 +64,11 @@
|
||||
|
||||
|
||||
<activity android:process=":qt_controlsActivity" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="io.guh.nymeaapp.NymeaAppControlsActivity" android:label="nymea:app" android:screenOrientation="unspecified" android:launchMode="standard">
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="nymea"/>
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.app.lib_name" android:value="service"/>
|
||||
<meta-data android:name="android.app.arguments" android:value="--controlActivity"/>
|
||||
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
||||
@ -95,6 +100,11 @@
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
|
||||
<service android:process=":qt_service" android:name="io.guh.nymeaapp.NymeaAppService">
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="xbmc"/>
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.app.lib_name" android:value="service"/>
|
||||
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
||||
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||
@ -145,4 +155,5 @@
|
||||
Remove the comment if you do not require these default features. -->
|
||||
<!-- %%INSERT_FEATURES -->
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
</manifest>
|
||||
|
||||
@ -61,9 +61,8 @@ public class NymeaAppControlService extends ControlsProviderService {
|
||||
}
|
||||
}
|
||||
@Override public void onUpdate(UUID nymeaId, UUID thingId) {
|
||||
Log.d(TAG, "onUpdate()");
|
||||
if (m_updatePublisher != null && m_activeControlIds.contains(thingId.toString())) {
|
||||
Log.d(TAG, "Updating publisher for thing: " + thingId);
|
||||
// Log.d(TAG, "Updating publisher for thing: " + thingId);
|
||||
m_updatePublisher.onNext(thingToControl(nymeaId, thingId));
|
||||
// m_updatePublisher.onComplete();
|
||||
}
|
||||
|
||||
@ -8,6 +8,9 @@ import android.telephony.TelephonyManager;
|
||||
import android.provider.Settings.Secure;
|
||||
import android.os.Vibrator;
|
||||
import android.os.Process;
|
||||
import android.nfc.NfcAdapter;
|
||||
import android.nfc.NdefMessage;
|
||||
import android.os.Parcelable;
|
||||
|
||||
// An activity spawned by android device controls on demand.
|
||||
|
||||
@ -26,11 +29,13 @@ public class NymeaAppControlsActivity extends org.qtproject.qt5.android.bindings
|
||||
Log.d(TAG, "Resuming...");
|
||||
}
|
||||
|
||||
|
||||
@Override public void onDestroy() {
|
||||
Log.d(TAG, "Destroying...");
|
||||
}
|
||||
|
||||
public boolean startedByNfc() {
|
||||
return NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction());
|
||||
}
|
||||
|
||||
public String nymeaId()
|
||||
{
|
||||
@ -47,5 +52,4 @@ public class NymeaAppControlsActivity extends org.qtproject.qt5.android.bindings
|
||||
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
|
||||
v.vibrate(duration);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -154,7 +154,6 @@ public class NymeaAppServiceConnection implements ServiceConnection {
|
||||
private BroadcastReceiver serviceMessageReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, "In OnReceive broadcast receiver");
|
||||
if (NymeaAppService.NYMEA_APP_BROADCAST.equals(intent.getAction())) {
|
||||
String payload = intent.getStringExtra("data");
|
||||
try {
|
||||
@ -170,7 +169,7 @@ public class NymeaAppServiceConnection implements ServiceConnection {
|
||||
{
|
||||
JSONObject data = new JSONObject(payload);
|
||||
JSONObject params = data.getJSONObject("params");
|
||||
Log.d(TAG, "Broadcast received from NymeaAppService: " + data.getString("notification"));
|
||||
// Log.d(TAG, "Broadcast received from NymeaAppService: " + data.getString("notification"));
|
||||
Log.d(TAG, params.toString());
|
||||
|
||||
if (data.getString("notification").equals("ThingStateChanged")) {
|
||||
@ -178,7 +177,7 @@ public class NymeaAppServiceConnection implements ServiceConnection {
|
||||
UUID thingId = UUID.fromString(params.getString("thingId"));
|
||||
UUID stateTypeId = UUID.fromString(params.getString("stateTypeId"));
|
||||
String value = params.getString("value");
|
||||
Log.d(TAG, "Thing state changed: " + thingId + " stateTypeId: " + stateTypeId + " value: " + value);
|
||||
// Log.d(TAG, "Thing state changed: " + thingId + " stateTypeId: " + stateTypeId + " value: " + value);
|
||||
|
||||
Thing thing = getThing(thingId);
|
||||
if (thing != null) {
|
||||
|
||||
Reference in New Issue
Block a user