More work on NFC
This commit is contained in:
parent
2508e4dd8a
commit
621bfaed91
@ -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 ¶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));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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";
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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 |
@ -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();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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