#include "devicecontrolapplication.h" #include "engine.h" #include "connection/discovery/nymeadiscovery.h" #include "connection/nymeahosts.h" #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 #include #include #include #include #include #include QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new PlatformHelperAndroid(); } DeviceControlApplication::DeviceControlApplication(int argc, char *argv[]) : QApplication(argc, argv) { setApplicationName("nymea-app"); setOrganizationName("nymea"); QSettings settings; m_discovery = new NymeaDiscovery(this); m_engine = new Engine(this); m_qmlEngine = new QQmlApplicationEngine(this); Nymea::Core::registerQmlTypes(); qmlRegisterSingletonType("Nymea", 1, 0, "PlatformHelper", platformHelperProvider); qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" ); qmlRegisterType("Nymea", 1, 0, "NfcThingActionWriter"); qmlRegisterSingletonType("Nymea", 1, 0, "NfcHelper", NfcHelper::nfcHelperProvider); StyleController *styleController = new StyleController("light", this); QQmlFileSelector *styleSelector = new QQmlFileSelector(m_qmlEngine); styleSelector->setExtraSelectors({styleController->currentStyle()}); foreach (const QFileInfo &fi, QDir(":/ui/fonts/").entryInfoList()) { QFontDatabase::addApplicationFont(fi.absoluteFilePath()); } foreach (const QFileInfo &fi, QDir(":/styles/" + styleController->currentStyle() + "/fonts/").entryInfoList()) { qDebug() << "Adding style font:" << fi.absoluteFilePath(); QFontDatabase::addApplicationFont(fi.absoluteFilePath()); } qmlRegisterSingletonType(QUrl("qrc:///styles/" + styleController->currentStyle() + "/Style.qml"), "Nymea", 1, 0, "Style" ); 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("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("nymeaId").toString(); QString thingId = QtAndroid::androidActivity().callObjectMethod("thingId").toString(); connectToNymea(nymeaId); m_qmlEngine->rootContext()->setContextProperty("controlledThingId", thingId); } } void DeviceControlApplication::handleNdefMessage(QNdefMessage message, QNearFieldTarget *target) { Q_UNUSED(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(), &ThingManager::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")); Thing *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> 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 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); } }