From 3ea0ec1f9f48cb115fdce147ac122a7af8b2771e Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Thu, 1 Oct 2020 16:56:16 +0200 Subject: [PATCH] Initial stab on NFC support --- androidservice/androidservice.pro | 2 +- .../controlviews/devicecontrolapplication.cpp | 47 ++++++++-- .../controlviews/devicecontrolapplication.h | 14 +++ .../nymeaappservice/nymeaappservice.h | 2 + nymea-app/images.qrc | 1 + nymea-app/main.cpp | 2 + nymea-app/nfchelper.cpp | 86 +++++++++++++++++ nymea-app/nfchelper.h | 42 +++++++++ nymea-app/nymea-app.pro | 6 +- nymea-app/ui/devicepages/DevicePageBase.qml | 18 +++- nymea-app/ui/images/nfc.svg | 94 +++++++++++++++++++ packaging/android/AndroidManifest.xml | 11 +++ .../nymeaapp/NymeaAppControlsActivity.java | 21 +++++ 13 files changed, 337 insertions(+), 9 deletions(-) create mode 100644 nymea-app/nfchelper.cpp create mode 100644 nymea-app/nfchelper.h create mode 100644 nymea-app/ui/images/nfc.svg diff --git a/androidservice/androidservice.pro b/androidservice/androidservice.pro index 41e7d52e..1b0cb54b 100644 --- a/androidservice/androidservice.pro +++ b/androidservice/androidservice.pro @@ -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) diff --git a/androidservice/controlviews/devicecontrolapplication.cpp b/androidservice/controlviews/devicecontrolapplication.cpp index 1b319967..70a3e5f3 100644 --- a/androidservice/controlviews/devicecontrolapplication.cpp +++ b/androidservice/controlviews/devicecontrolapplication.cpp @@ -13,6 +13,7 @@ #include #include #include +#include QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { @@ -26,26 +27,31 @@ DeviceControlApplication::DeviceControlApplication(int argc, char *argv[]) : QAp setApplicationName("nymea-app"); setOrganizationName("nymea"); + QNearFieldManager *manager = new QNearFieldManager(this); + int ret = manager->registerNdefMessageHandler(this, SLOT(handleNdefMessage(QNdefMessage,QNearFieldTarget*))); + qDebug() << "*** NFC registered" << ret; + QString nymeaId = QtAndroid::androidActivity().callObjectMethod("nymeaId").toString(); QString thingId = QtAndroid::androidActivity().callObjectMethod("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()); + NymeaHost *host = m_discovery->nymeaHosts()->find(nymeaId); - if (!host) { + if (nymeaId.isEmpty() && !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); + m_engine = new Engine(this); + + m_engine->jsonRpcClient()->connectToHost(host); qDebug() << "Connecting to:" << host; - m_engine->jsonRpcClient()->connectToHost(host); qDebug() << "Creating QML view"; QQmlApplicationEngine *qmlEngine = new QQmlApplicationEngine(this); @@ -64,3 +70,32 @@ DeviceControlApplication::DeviceControlApplication(int argc, char *argv[]) : QAp qmlEngine->load(QUrl(QLatin1String("qrc:/Main.qml"))); } +void DeviceControlApplication::handleNdefMessage(QNdefMessage message, QNearFieldTarget *target) +{ + qDebug() << "************* NFC message!" << message.toByteArray() << target; + foreach (const QNdefRecord &record, message) { + QNdefNfcUriRecord uriRecord(record); + qDebug() << "record" << uriRecord.uri(); + QUrl url = uriRecord.uri(); + QUuid nymeaId = QUuid(url.host().split('.').first()); + QUuid thingId = QUuid(url.host().split('.').last()); + QList> queryItems = QUrlQuery(url.query()).queryItems(); + for (int i = 0; i < queryItems.count(); i++) { + QUuid stateTypeId = queryItems.at(i).first; + QVariant value = queryItems.at(i).second; + + } + + NymeaHost *host = m_discovery->nymeaHosts()->find(nymeaId); + m_engine->jsonRpcClient()->connectToHost(host); + qmlEngine->rootContext()->setContextProperty("controlledThingId", thingId); + + } +} + +void DeviceControlApplication::createView() +{ + +} + + diff --git a/androidservice/controlviews/devicecontrolapplication.h b/androidservice/controlviews/devicecontrolapplication.h index 4c80e850..ef1fb332 100644 --- a/androidservice/controlviews/devicecontrolapplication.h +++ b/androidservice/controlviews/devicecontrolapplication.h @@ -2,6 +2,11 @@ #define DEVICECONTROLAPPLICATION_H #include +#include +#include + +#include "connection/discovery/nymeadiscovery.h" +#include "engine.h" class DeviceControlApplication : public QApplication { @@ -9,6 +14,15 @@ class DeviceControlApplication : public QApplication public: explicit DeviceControlApplication(int argc, char *argv[]); +private slots: + void handleNdefMessage(QNdefMessage message,QNearFieldTarget* target); + + void createView(); + +private: + NymeaDiscovery *m_discovery = nullptr; + Engine *m_engine = nullptr; + }; #endif // DEVICECONTROLAPPLICATION_H diff --git a/androidservice/nymeaappservice/nymeaappservice.h b/androidservice/nymeaappservice/nymeaappservice.h index 6f6e653a..5e823efa 100644 --- a/androidservice/nymeaappservice/nymeaappservice.h +++ b/androidservice/nymeaappservice/nymeaappservice.h @@ -2,6 +2,8 @@ #define NYMEAAPPSERVICE_H #include +#include +#include #include "engine.h" diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index 265e31b1..0e2c299e 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -236,5 +236,6 @@ ui/images/connections/nm-signal-100-secure.svg ui/images/connections/bluetooth.svg ui/images/connections/network-wired-disabled.svg + ui/images/nfc.svg diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index 680de44b..f1b0595b 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -52,6 +52,7 @@ #include "pushnotifications.h" #include "applogcontroller.h" #include "ruletemplates/messages.h" +#include "nfchelper.h" QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { @@ -124,6 +125,7 @@ int main(int argc, char *argv[]) QQmlApplicationEngine *engine = new QQmlApplicationEngine(); qmlRegisterSingletonType("Nymea", 1, 0, "PlatformHelper", platformHelperProvider); + qmlRegisterType("Nymea", 1, 0, "NfcHelper"); PushNotifications::instance()->connectClient(); qmlRegisterSingletonType("Nymea", 1, 0, "PushNotifications", PushNotifications::pushNotificationsProvider); diff --git a/nymea-app/nfchelper.cpp b/nymea-app/nfchelper.cpp new file mode 100644 index 00000000..a93929aa --- /dev/null +++ b/nymea-app/nfchelper.cpp @@ -0,0 +1,86 @@ +#include "nfchelper.h" +#include "types/deviceclass.h" +#include "types/statetype.h" + +#include +#include +#include +#include +#include +#include + +NfcHelper::NfcHelper(QObject *parent) : QObject(parent) +{ + m_manager = new QNearFieldManager(this); + connect(m_manager, &QNearFieldManager::targetDetected, this, &NfcHelper::targetDetected); + connect(m_manager, &QNearFieldManager::targetLost, this, &NfcHelper::targetLost); +} + +bool NfcHelper::busy() const +{ + return m_busy; +} + +void NfcHelper::writeThingStates(Engine *engine, Device *thing) +{ + if (m_busy) { + return; + } + + QUrl url; + url.setScheme("nymea"); + url.setHost(engine->jsonRpcClient()->currentHost()->uuid().toString().remove(QRegExp("[{}]")) + "." + thing->id().toString().remove(QRegExp("[{}]"))); + QUrlQuery query; + for (int i = 0; i < thing->thingClass()->stateTypes()->rowCount(); i++) { + StateType *stateType = thing->thingClass()->stateTypes()->get(i); + ActionType *actionType = thing->thingClass()->actionTypes()->getActionType(stateType->id()); + if (!actionType) { + continue; // Read only state + } + QVariant currentValue = thing->states()->getState(stateType->id())->value(); + query.addQueryItem(stateType->id().toString().remove(QRegExp("[{}]")), currentValue.toString()); + } + url.setQuery(query); + qDebug() << "writing message" << url; + + QNdefNfcUriRecord record; + record.setUri(url); + QNdefMessage message; + message.append(record); + + m_currentMessage = message; + m_manager->startTargetDetection(); + m_busy = true; + emit busyChanged(); +} + +void NfcHelper::targetDetected(QNearFieldTarget *target) +{ + connect(target, &QNearFieldTarget::ndefMessagesWritten, this, &NfcHelper::ndefMessageWritten); + connect(target, &QNearFieldTarget::error, this, &NfcHelper::targetError); + + + QNearFieldTarget::RequestId m_request = target->writeNdefMessages(QList() << m_currentMessage); + if (!m_request.isValid()) { + qDebug() << "Error writing tag"; + //targetError(QNearFieldTarget::NdefWriteError, m_request); + } +} + +void NfcHelper::targetLost(QNearFieldTarget *target) +{ + qDebug() << "Target lost" << target; +} + +void NfcHelper::ndefMessageWritten() +{ + qDebug() << "NDEF message written"; + m_manager->stopTargetDetection(); + m_busy = false; + emit busyChanged(); +} + +void NfcHelper::targetError() +{ + qDebug() << "Target error"; +} diff --git a/nymea-app/nfchelper.h b/nymea-app/nfchelper.h new file mode 100644 index 00000000..a2df5a3d --- /dev/null +++ b/nymea-app/nfchelper.h @@ -0,0 +1,42 @@ +#ifndef NFCHELPER_H +#define NFCHELPER_H + +#include +#include +#include + +#include "types/device.h" +#include "engine.h" + +class NfcHelper : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) + +public: + explicit NfcHelper(QObject *parent = nullptr); + + bool busy() const; + +public slots: + void writeThingStates(Engine *engine, Device *thing); + +signals: + void busyChanged(); + +private slots: + void targetDetected(QNearFieldTarget *target); + void targetLost(QNearFieldTarget *target); + + void ndefMessageWritten(); + void targetError(); + +private: + QNearFieldManager *m_manager = nullptr; + bool m_busy = false; + + QNdefMessage m_currentMessage; + +}; + +#endif // NFCHELPER_H diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 8bb1b85e..ffaedaa8 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -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,7 @@ PRE_TARGETDEPS += ../libnymea-app HEADERS += \ mainmenumodel.h \ + nfchelper.h \ platformintegration/generic/raspberrypihelper.h \ stylecontroller.h \ pushnotifications.h \ @@ -24,6 +25,7 @@ HEADERS += \ SOURCES += main.cpp \ mainmenumodel.cpp \ + nfchelper.cpp \ platformintegration/generic/raspberrypihelper.cpp \ stylecontroller.cpp \ pushnotifications.cpp \ @@ -162,3 +164,5 @@ BR=$$BRANDING target.path = /usr/bin INSTALLS += target + +ANDROID_ABIS = armeabi-v7a arm64-v8a diff --git a/nymea-app/ui/devicepages/DevicePageBase.qml b/nymea-app/ui/devicepages/DevicePageBase.qml index 502cb107..794ffec1 100644 --- a/nymea-app/ui/devicepages/DevicePageBase.qml +++ b/nymea-app/ui/devicepages/DevicePageBase.qml @@ -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,6 +111,14 @@ Page { functionName: "addToGroup" })) } + + 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 +146,14 @@ Page { pageStack.push(Qt.resolvedUrl("DeviceLogPage.qml"), {device: root.device }); } + NfcHelper { + id: nfcHelper + } + + function writeNfcTag() { + nfcHelper.writeThingStates(engine, root.thing) + } + Component { id: menuEntryComponent IconMenuItem { diff --git a/nymea-app/ui/images/nfc.svg b/nymea-app/ui/images/nfc.svg new file mode 100644 index 00000000..dea348bd --- /dev/null +++ b/nymea-app/ui/images/nfc.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/packaging/android/AndroidManifest.xml b/packaging/android/AndroidManifest.xml index b8380cf5..5fea09f0 100644 --- a/packaging/android/AndroidManifest.xml +++ b/packaging/android/AndroidManifest.xml @@ -64,6 +64,11 @@ + + + + + @@ -95,6 +100,11 @@ + + + + + @@ -145,4 +155,5 @@ Remove the comment if you do not require these default features. --> + diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java index ffa9f070..466f66e0 100644 --- a/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java +++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java @@ -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. @@ -48,4 +51,22 @@ public class NymeaAppControlsActivity extends org.qtproject.qt5.android.bindings v.vibrate(duration); } + @Override + protected void onNewIntent(Intent intent) { + Log.d(TAG, "*************** New intent"); + super.onNewIntent(intent); + if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) { + Parcelable[] rawMessages = + intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); + if (rawMessages != null) { + NdefMessage[] messages = new NdefMessage[rawMessages.length]; + for (int i = 0; i < rawMessages.length; i++) { + messages[i] = (NdefMessage) rawMessages[i]; + Log.d(TAG, messages[i].toString()); + } + // Process the messages array. + } + } + } + }