More work on NFC

This commit is contained in:
Michael Zanetti 2020-10-12 14:59:15 +02:00
parent 2508e4dd8a
commit 621bfaed91
11 changed files with 487 additions and 242 deletions

View File

@ -14,6 +14,7 @@
#include <QtQml>
#include <QtAndroid>
#include <QAndroidJniObject>
#include <QAndroidIntent>
#include <QNdefNfcUriRecord>
QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
@ -28,75 +29,180 @@ 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<jstring>("nymeaId").toString();
QString thingId = QtAndroid::androidActivity().callObjectMethod<jstring>("thingId").toString();
QSettings settings;
m_discovery = new NymeaDiscovery(this);
AWSClient::instance()->setConfig(settings.value("cloudEnvironment").toString());
m_discovery->setAwsClient(AWSClient::instance());
NymeaHost *host = m_discovery->nymeaHosts()->find(nymeaId);
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);
}
m_engine = new Engine(this);
m_engine->jsonRpcClient()->connectToHost(host);
qDebug() << "Connecting to:" << host;
qDebug() << "Creating QML view";
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<NfcHelper>("Nymea", 1, 0, "NfcHelper");
StyleController styleController;
m_qmlEngine->rootContext()->setContextProperty("styleController", &styleController);
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", thingId);
m_qmlEngine->rootContext()->setContextProperty("controlledThingId", ""); // Unknown at this point
m_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<QPair<QString, QString>> 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;
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*)));
NymeaHost *host = m_discovery->nymeaHosts()->find(nymeaId);
m_engine->jsonRpcClient()->connectToHost(host);
} 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::createView()
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);
}
qDebug() << "Connecting to:" << host->name();
m_engine->jsonRpcClient()->connectToHost(host);
}
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;
}
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 &paramString, 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));
}
}
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);
}
m_engine->thingManager()->executeAction(thingId, actionType->id(), params);
}
}

View File

@ -5,7 +5,9 @@
#include <QNearFieldManager>
#include <QNdefMessage>
#include <QQmlApplicationEngine>
#include <QNdefMessage>
#include "types/ruleactions.h"
#include "connection/discovery/nymeadiscovery.h"
#include "engine.h"
@ -18,12 +20,18 @@ public:
private slots:
void handleNdefMessage(QNdefMessage message,QNearFieldTarget* target);
void createView();
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

View File

@ -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

View File

@ -1,6 +1,9 @@
#include "nfchelper.h"
#include "types/deviceclass.h"
#include "types/statetype.h"
#include "types/ruleaction.h"
#include "types/ruleactionparams.h"
#include "types/ruleactionparam.h"
#include <QNearFieldManager>
#include <QNdefMessage>
@ -9,36 +12,126 @@
#include <QUrl>
#include <QUrlQuery>
NfcHelper::NfcHelper(QObject *parent) : QObject(parent)
NfcHelper::NfcHelper(QObject *parent):
QObject(parent),
m_manager(new QNearFieldManager(this)),
m_actions(new RuleActions(this))
{
m_manager = new QNearFieldManager(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();
}
bool NfcHelper::busy() const
NfcHelper::~NfcHelper()
{
return m_busy;
m_manager->stopTargetDetection();
}
void NfcHelper::writeThingStates(Engine *engine, Device *thing)
Engine *NfcHelper::engine() const
{
if (m_busy) {
return;
return m_engine;
}
void NfcHelper::setEngine(Engine *engine)
{
if (m_engine != engine) {
m_engine = engine;
emit engineChanged();
updateContent();
}
}
Device *NfcHelper::thing() const
{
return m_thing;
}
void NfcHelper::setThing(Device *thing)
{
if (m_thing != thing) {
m_thing = thing;
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");
url.setHost(engine->jsonRpcClient()->currentHost()->uuid().toString().remove(QRegExp("[{}]")) + "." + thing->id().toString().remove(QRegExp("[{}]")));
if (!m_engine || !m_thing) {
return;
}
url.setHost(m_engine->jsonRpcClient()->currentHost()->uuid().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
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;
}
QVariant currentValue = thing->states()->getState(stateType->id())->value();
query.addQueryItem(stateType->id().toString().remove(QRegExp("[{}]")), currentValue.toString());
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;
@ -49,38 +142,40 @@ void NfcHelper::writeThingStates(Engine *engine, Device *thing)
message.append(record);
m_currentMessage = message;
m_manager->startTargetDetection();
m_busy = true;
emit busyChanged();
emit messageSizeChanged();
}
void NfcHelper::targetDetected(QNearFieldTarget *target)
{
connect(target, &QNearFieldTarget::ndefMessagesWritten, this, &NfcHelper::ndefMessageWritten);
connect(target, &QNearFieldTarget::error, this, &NfcHelper::targetError);
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";
//targetError(QNearFieldTarget::NdefWriteError, m_request);
m_status = TagStatusFailed;
emit statusChanged();
}
m_status = TagStatusWriting;
emit statusChanged();
}
void NfcHelper::targetLost(QNearFieldTarget *target)
{
qDebug() << "Target lost" << target;
m_status = TagStatusWaiting;
emit statusChanged();
}
void NfcHelper::ndefMessageWritten()
{
qDebug() << "NDEF message written";
m_manager->stopTargetDetection();
m_busy = false;
emit busyChanged();
}
void NfcHelper::targetError()
{
qDebug() << "Target error";
}

View File

@ -7,33 +7,62 @@
#include "types/device.h"
#include "engine.h"
#include "types/ruleactions.h"
class NfcHelper : public QObject
{
Q_OBJECT
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
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)
explicit NfcHelper(QObject *parent = nullptr);
~NfcHelper();
bool busy() const;
Engine *engine() const;
void setEngine(Engine *engine);
public slots:
void writeThingStates(Engine *engine, Device *thing);
Device *thing() const;
void setThing(Device *thing);
RuleActions *actions() const;
int messageSize() const;
TagStatus status() const;
signals:
void busyChanged();
void engineChanged();
void thingChanged();
void messageSizeChanged();
void statusChanged();
private slots:
void updateContent();
void targetDetected(QNearFieldTarget *target);
void targetLost(QNearFieldTarget *target);
void ndefMessageWritten();
void targetError();
private:
QNearFieldManager *m_manager = nullptr;
bool m_busy = false;
Engine *m_engine = nullptr;
Device *m_thing = nullptr;
RuleActions* m_actions;
TagStatus m_status = TagStatusWaiting;
QNdefMessage m_currentMessage;

View File

@ -165,10 +165,9 @@ BR=$$BRANDING
target.path = /usr/bin
INSTALLS += target
ANDROID_ABIS = armeabi-v7a
contains(ANDROID_TARGET_ARCH,) {
ANDROID_ABIS = \
armeabi-v7a \
arm64-v8a
}

View File

@ -21,7 +21,7 @@
<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 21.92185,21.147163 c 2.283959,1.18636 3.037486,-1.033283 -0.0035,-5.561123 L 42.236652,53.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 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 60.072038 z"
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" />
@ -38,9 +38,9 @@
inkscape:window-height="873"
id="namedview7"
showgrid="true"
inkscape:zoom="2.1413287"
inkscape:cx="32.892399"
inkscape:cy="42.615368"
inkscape:zoom="2.4135194"
inkscape:cx="42.404861"
inkscape:cy="39.439781"
inkscape:window-x="60"
inkscape:window-y="27"
inkscape:window-maximized="1"
@ -89,6 +89,6 @@
<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 32.079371,16.112732 c -2.283959,-1.18636 -3.037486,1.033283 0.0035,5.561123 l 21.722882,20.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 -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 16.074877 Z"
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>

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -42,13 +42,16 @@ Page {
header: NymeaHeader {
text: qsTr("Write NFC tag")
onBackPressed: {
root.backPressed();
pageStack.pop()
}
}
NfcHelper {
id: nfcHelper
engine: _engine
thing: root.thing
}
// nfcHelper.writeThingStates(engine, root.thing)
@ -56,140 +59,163 @@ Page {
anchors.fill: parent
columns: app.landscape ? 2 : 1
Item {
Layout.preferredWidth: Math.min(root.width / parent.columns, root.height)
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
}
}
ParallelAnimation {
loops: 2
NumberAnimation {
duration: 250
target: vibrateCircle
property: "scale"
from: .8
to: 1.5
}
NumberAnimation {
duration: 250
target: vibrateCircle
property: "opacity"
from: 1
to: 0
}
}
PauseAnimation {
duration: 500
}
NumberAnimation {
target: phoneIcon
property: "opacity"
duration: 500
to: 0
}
}
ColorIcon {
id: nfcIcon
name: "../images/nfc.svg"
height: app.iconSize * 2
width: app.iconSize * 2
anchors.centerIn: parent
anchors.horizontalCenterOffset: - app.iconSize * 2
}
Item {
id: phoneIcon
height: app.iconSize * 5
width: app.iconSize * 5
scale: 1.5
anchors.centerIn: parent
anchors.horizontalCenterOffset: app.iconSize * 2
Rectangle {
id: vibrateCircle
anchors.centerIn: parent
anchors.fill: parent
radius: width / 2
color: "transparent"
// border.color: nfcIcon.keyColor
border.color: app.accentColor
border.width: 2
scale: .8
opacity: 0
}
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
}
}
}
ColumnLayout {
Layout.preferredWidth: parent.width / parent.columns
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
text: qsTr("Select the wanted states and tap an NFC tag to store them. When tapping this tag later, they will be restored.").arg(root.thing.name)
text: {
switch (nfcHelper.status) {
case NfcHelper.TagStatusWaiting:
return qsTr("Tap an NFC tag to link it to %1.").arg(root.thing.name)
case NfcHelper.TagStatusWriting:
return qsTr("Writing NFC tag...")
case NfcHelper.TagStatusWritten:
return qsTr("NFC tag linked to %1.").arg(root.thing.name)
case NfcHelper.TagStatusFailed:
return qsTr("Failed linking the NFC tag to %1.").arg(root.thing.name)
}
}
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
font.pixelSize: app.smallFont
}
Label {
Layout.fillWidth: true
text: qsTr("Required tag size: %1 bytes").arg(nfcHelper.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: nfcHelper.status == NfcHelper.TagStatusWaiting
}
Item {
id: phoneIcon
height: app.iconSize * 5
width: app.iconSize * 5
scale: 1.5
anchors.centerIn: parent
anchors.horizontalCenterOffset: app.iconSize * 2
visible: nfcHelper.status == NfcHelper.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: nfcHelper.status == NfcHelper.TagStatusWaiting ? 0 : 1
Behavior on opacity { NumberAnimation { duration: 300 } }
property bool shown: nfcHelper.status == NfcHelper.TagStatusWritten || nfcHelper.status == NfcHelper.TagStatusFailed
BusyIndicator {
anchors.fill: parent
running: visible
visible: nfcHelper.status == NfcHelper.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: nfcHelper.status == NfcHelper.TagStatusFailed ? "../images/close.svg" : "../images/tick.svg"
color: nfcHelper.status == NfcHelper.TagStatusFailed ? "red" : "green"
}
}
}
}
}
ColumnLayout {
Layout.preferredWidth: parent.width / parent.columns
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: root.thingClass.stateTypes
model: nfcHelper.actions
clip: true
delegate: CheckDelegate {
delegate: RuleActionDelegate {
ruleAction: nfcHelper.actions.get(index)
width: parent.width
text: model.displayName
checked: true
onRemoveRuleAction: nfcHelper.actions.removeRuleAction(index)
}
}
Button {
text: qsTr("Add action")
Layout.fillWidth: true
Layout.margins: app.margins
onClicked: {
var action = nfcHelper.actions.createNewRuleAction()
action.thingId = root.thing.id
var page = pageStack.push("SelectRuleActionPage.qml", {ruleAction: action});
page.done.connect(function() {
nfcHelper.actions.addRuleAction(action);
pageStack.pop();
})
page.backPressed.connect(function() {
action.destroy()
pageStack.pop();
})
}
}
}

View File

@ -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();
}

View File

@ -29,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()
{
@ -50,23 +52,4 @@ public class NymeaAppControlsActivity extends org.qtproject.qt5.android.bindings
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
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.
}
}
}
}

View File

@ -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) {