Merge PR #432: Android device controls
This commit is contained in:
commit
fa08e657f1
52
androidservice/androidservice.pro
Normal file
52
androidservice/androidservice.pro
Normal file
@ -0,0 +1,52 @@
|
||||
TEMPLATE = lib
|
||||
TARGET = service
|
||||
CONFIG += dll
|
||||
QT += core androidextras
|
||||
QT += network qml quick quickcontrols2 svg websockets bluetooth charts
|
||||
|
||||
include(../config.pri)
|
||||
include(../android_openssl/openssl.pri)
|
||||
|
||||
|
||||
INCLUDEPATH += $$top_srcdir/libnymea-app/
|
||||
|
||||
# https://bugreports.qt.io/browse/QTBUG-83165
|
||||
LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH}
|
||||
|
||||
LIBS += -L$$top_builddir/libnymea-app/ -lnymea-app
|
||||
PRE_TARGETDEPS += ../libnymea-app
|
||||
|
||||
RESOURCES += controlviews/controlviews.qrc \
|
||||
../nymea-app/resources.qrc \
|
||||
../nymea-app/images.qrc \
|
||||
../nymea-app/styles.qrc
|
||||
|
||||
INCLUDEPATH += ../nymea-app/
|
||||
|
||||
SOURCES += \
|
||||
controlviews/devicecontrolapplication.cpp \
|
||||
nymeaappservice/nymeaappservice.cpp \
|
||||
nymeaappservice/androidbinder.cpp \
|
||||
../nymea-app/stylecontroller.cpp \
|
||||
../nymea-app/platformhelper.cpp \
|
||||
../nymea-app/platformintegration/android/platformhelperandroid.cpp \
|
||||
service_main.cpp
|
||||
|
||||
HEADERS += \
|
||||
controlviews/devicecontrolapplication.h \
|
||||
nymeaappservice/nymeaappservice.h \
|
||||
nymeaappservice/androidbinder.h \
|
||||
../nymea-app/stylecontroller.h \
|
||||
../nymea-app/platformhelper.h \
|
||||
../nymea-app/platformintegration/android/platformhelperandroid.h \
|
||||
|
||||
DISTFILES += \
|
||||
../packaging/android/src/io/guh/nymeaapp/Action.java \
|
||||
../packaging/android/src/io/guh/nymeaapp/NymeaAppControlService.java \
|
||||
../packaging/android/src/io/guh/nymeaapp/NymeaAppService.java \
|
||||
../packaging/android/src/io/guh/nymeaapp/NymeaAppControlsActivity.java \
|
||||
../packaging/android/src/io/guh/nymeaapp/NymeaAppServiceConnection.java \
|
||||
../packaging/android/src/io/guh/nymeaapp/Thing.java \
|
||||
../packaging/android/src/io/guh/nymeaapp/State.java \
|
||||
controlviews/Main.qml
|
||||
|
||||
57
androidservice/controlviews/Main.qml
Normal file
57
androidservice/controlviews/Main.qml
Normal file
@ -0,0 +1,57 @@
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.2
|
||||
import Qt.labs.settings 1.0
|
||||
import Nymea 1.0
|
||||
import "qrc:/ui/devicepages/"
|
||||
|
||||
ApplicationWindow {
|
||||
id: app
|
||||
visible: true
|
||||
visibility: ApplicationWindow.FullScreen
|
||||
|
||||
color: Material.background
|
||||
|
||||
// Those variables must be present in the Style
|
||||
title: appName
|
||||
Material.primary: primaryColor
|
||||
Material.accent: accentColor
|
||||
Material.foreground: foregroundColor
|
||||
|
||||
property int margins: 16
|
||||
property int bigMargins: 20
|
||||
property int extraSmallFont: 10
|
||||
property int smallFont: 13
|
||||
property int mediumFont: 16
|
||||
property int largeFont: 20
|
||||
property int iconSize: 30
|
||||
property int delegateHeight: 60
|
||||
property color backgroundColor: Material.background
|
||||
|
||||
readonly property bool landscape: app.width > app.height
|
||||
|
||||
ThingsProxy {
|
||||
id: thingProxy
|
||||
engine: _engine
|
||||
filterDeviceId: controlledThingId
|
||||
}
|
||||
|
||||
property Thing controlledThing: engine.thingManager.fetchingData ? null : engine.thingManager.things.getThing(controlledThingId)
|
||||
|
||||
onControlledThingChanged: {
|
||||
loader.setSource("qrc:/ui/devicepages/" + NymeaUtils.interfaceListToDevicePage(controlledThing.thingClass.interfaces), {thing: controlledThing, header: null})
|
||||
PlatformHelper.hideSplashScreen();
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: app.margins // For some reason the bottom edge seems a bit off in the overlay
|
||||
}
|
||||
|
||||
onClosing: {
|
||||
print("************* Control View closing")
|
||||
}
|
||||
|
||||
}
|
||||
5
androidservice/controlviews/controlviews.qrc
Normal file
5
androidservice/controlviews/controlviews.qrc
Normal file
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>Main.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
66
androidservice/controlviews/devicecontrolapplication.cpp
Normal file
66
androidservice/controlviews/devicecontrolapplication.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
#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/platformintegration/android/platformhelperandroid.h"
|
||||
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QtDebug>
|
||||
#include <QtQml>
|
||||
#include <QtAndroid>
|
||||
#include <QAndroidJniObject>
|
||||
|
||||
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");
|
||||
|
||||
QString nymeaId = QtAndroid::androidActivity().callObjectMethod<jstring>("nymeaId").toString();
|
||||
QString thingId = QtAndroid::androidActivity().callObjectMethod<jstring>("thingId").toString();
|
||||
|
||||
QSettings settings;
|
||||
|
||||
NymeaDiscovery *discovery = new NymeaDiscovery(this);
|
||||
AWSClient::instance()->setConfig(settings.value("cloudEnvironment").toString());
|
||||
discovery->setAwsClient(AWSClient::instance());
|
||||
NymeaHost *host = 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;
|
||||
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")));
|
||||
}
|
||||
|
||||
14
androidservice/controlviews/devicecontrolapplication.h
Normal file
14
androidservice/controlviews/devicecontrolapplication.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef DEVICECONTROLAPPLICATION_H
|
||||
#define DEVICECONTROLAPPLICATION_H
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
class DeviceControlApplication : public QApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeviceControlApplication(int argc, char *argv[]);
|
||||
|
||||
};
|
||||
|
||||
#endif // DEVICECONTROLAPPLICATION_H
|
||||
115
androidservice/nymeaappservice/androidbinder.cpp
Normal file
115
androidservice/nymeaappservice/androidbinder.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
#include "androidbinder.h"
|
||||
#include "engine.h"
|
||||
#include "types/device.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QAndroidParcel>
|
||||
#include <QAndroidJniObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QtAndroid>
|
||||
|
||||
AndroidBinder::AndroidBinder(NymeaAppService *service):
|
||||
m_service(service)
|
||||
{
|
||||
}
|
||||
|
||||
bool AndroidBinder::onTransact(int code, const QAndroidParcel &data, const QAndroidParcel &reply, QAndroidBinder::CallType flags)
|
||||
{
|
||||
qDebug() << "onTransact: code " << code << ", flags " << int(flags);
|
||||
|
||||
// QString payload = data.readData();
|
||||
QString payload = data.handle().callObjectMethod<jstring>("readString").toString();
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(payload.toUtf8(), &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error parsing JSON from parcel:" << error.errorString();
|
||||
qWarning() << payload;
|
||||
return false;
|
||||
}
|
||||
QVariantMap request = jsonDoc.toVariant().toMap();
|
||||
|
||||
if (request.value("method").toString() == "GetInstances") {
|
||||
QVariantMap params;
|
||||
QVariantList instances;
|
||||
foreach (const QUuid &nymeaId, m_service->engines().keys()) {
|
||||
Engine *engine = m_service->engines().value(nymeaId);
|
||||
QVariantMap instance;
|
||||
instance.insert("id", nymeaId);
|
||||
instance.insert("isReady", engine->jsonRpcClient()->connected() && !engine->thingManager()->fetchingData());
|
||||
instance.insert("name", engine->jsonRpcClient()->currentHost()->name());
|
||||
instances.append(instance);
|
||||
}
|
||||
params.insert("instances", instances);
|
||||
sendReply(reply, params);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (request.value("method").toString() == "GetThings") {
|
||||
QUuid nymeaId = request.value("params").toMap().value("nymeaId").toUuid();
|
||||
Engine *engine = m_service->engines().value(nymeaId);
|
||||
if (!engine) {
|
||||
qWarning() << "Android client requested things for an invalid nymea instance:" << nymeaId;
|
||||
return false;
|
||||
}
|
||||
QVariantList thingsList;
|
||||
for (int i = 0; i < engine->thingManager()->things()->rowCount(); i++) {
|
||||
Device *thing = engine->thingManager()->things()->get(i);
|
||||
QVariantMap thingMap;
|
||||
thingMap.insert("id", thing->id());
|
||||
thingMap.insert("name", thing->name());
|
||||
thingMap.insert("className", thing->thingClass()->displayName());
|
||||
thingMap.insert("interfaces", thing->thingClass()->interfaces());
|
||||
QVariantList states;
|
||||
for (int j = 0; j < thing->states()->rowCount(); j++) {
|
||||
State *state = thing->states()->get(j);
|
||||
QVariantMap stateMap;
|
||||
stateMap.insert("stateTypeId", state->stateTypeId());
|
||||
stateMap.insert("name", thing->thingClass()->stateTypes()->getStateType(state->stateTypeId())->name());
|
||||
stateMap.insert("displayName", thing->thingClass()->stateTypes()->getStateType(state->stateTypeId())->displayName());
|
||||
stateMap.insert("value", state->value());
|
||||
states.append(stateMap);
|
||||
}
|
||||
thingMap.insert("states", states);
|
||||
QVariantList actions;
|
||||
for (int j = 0; j < thing->thingClass()->actionTypes()->rowCount(); j++) {
|
||||
ActionType *actionType = thing->thingClass()->actionTypes()->get(j);
|
||||
QVariantMap actionMap;
|
||||
actionMap.insert("actionTypeId", actionType->id());
|
||||
actionMap.insert("name", actionType->name());
|
||||
actionMap.insert("displayName", actionType->displayName());
|
||||
actions.append(actionMap);
|
||||
}
|
||||
thingMap.insert("actions", actions);
|
||||
thingsList.append(thingMap);
|
||||
}
|
||||
QVariantMap params;
|
||||
params.insert("things", thingsList);
|
||||
sendReply(reply, params);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (request.value("method").toString() == "ExecuteAction") {
|
||||
qDebug() << "ExecuteAction";
|
||||
QUuid nymeaId = request.value("params").toMap().value("nymeaId").toUuid();
|
||||
Engine *engine = m_service->engines().value(nymeaId);
|
||||
if (!engine) {
|
||||
qWarning() << "Android client requested executeAction for an invalid nymea instance:" << nymeaId;
|
||||
return false;
|
||||
}
|
||||
QUuid thingId = request.value("params").toMap().value("thingId").toUuid();
|
||||
QUuid actionTypeId = request.value("params").toMap().value("actionTypeId").toUuid();
|
||||
QVariantList params = request.value("params").toMap().value("params").toList();
|
||||
|
||||
qDebug() << "**** executeAction:" << thingId << actionTypeId << params;
|
||||
engine->thingManager()->executeAction(thingId, actionTypeId, params);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AndroidBinder::sendReply(const QAndroidParcel &reply, const QVariantMap ¶ms)
|
||||
{
|
||||
QString payload = QJsonDocument::fromVariant(params).toJson();
|
||||
reply.handle().callMethod<void>("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(payload).object<jstring>());
|
||||
}
|
||||
23
androidservice/nymeaappservice/androidbinder.h
Normal file
23
androidservice/nymeaappservice/androidbinder.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef ANDROIDBINDER_H
|
||||
#define ANDROIDBINDER_H
|
||||
|
||||
#include <QAndroidBinder>
|
||||
|
||||
#include "nymeaappservice.h"
|
||||
#include "engine.h"
|
||||
|
||||
class AndroidBinder : public QAndroidBinder
|
||||
{
|
||||
public:
|
||||
explicit AndroidBinder(NymeaAppService *service);
|
||||
|
||||
bool onTransact(int code, const QAndroidParcel &data, const QAndroidParcel &reply, QAndroidBinder::CallType flags) override;
|
||||
|
||||
private:
|
||||
void sendReply(const QAndroidParcel &reply, const QVariantMap ¶ms);
|
||||
|
||||
private:
|
||||
NymeaAppService *m_service = nullptr;
|
||||
};
|
||||
|
||||
#endif // ANDROIDBINDER_H
|
||||
83
androidservice/nymeaappservice/nymeaappservice.cpp
Normal file
83
androidservice/nymeaappservice/nymeaappservice.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include "nymeaappservice.h"
|
||||
#include "androidbinder.h"
|
||||
|
||||
#include <QtAndroid>
|
||||
#include <QDebug>
|
||||
#include <QSettings>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "connection/discovery/nymeadiscovery.h"
|
||||
#include "connection/nymeahosts.h"
|
||||
|
||||
NymeaAppService::NymeaAppService(int argc, char **argv):
|
||||
QAndroidService(argc, argv, [=](const QAndroidIntent &) {
|
||||
return new AndroidBinder{this};
|
||||
})
|
||||
{
|
||||
setApplicationName("nymea-app");
|
||||
setOrganizationName("nymea");
|
||||
|
||||
QSettings settings;
|
||||
|
||||
NymeaDiscovery *discovery = new NymeaDiscovery(this);
|
||||
AWSClient::instance()->setConfig(settings.value("cloudEnvironment").toString());
|
||||
discovery->setAwsClient(AWSClient::instance());
|
||||
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
settings.beginGroup(QString("tabSettings%1").arg(i));
|
||||
QUuid lastConnected = settings.value("lastConnectedHost").toUuid();
|
||||
settings.endGroup();
|
||||
|
||||
if (lastConnected.isNull()) {
|
||||
continue;
|
||||
}
|
||||
NymeaHost *host = discovery->nymeaHosts()->find(lastConnected);
|
||||
if (!host) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Engine *engine = new Engine(this);
|
||||
engine->jsonRpcClient()->connectToHost(host);
|
||||
m_engines.insert(host->uuid(), engine);
|
||||
|
||||
|
||||
QObject::connect(engine->thingManager(), &DeviceManager::thingStateChanged, [=](const QUuid &thingId, const QUuid &stateTypeId, const QVariant &value){
|
||||
QVariantMap params;
|
||||
params.insert("nymeaId", engine->jsonRpcClient()->currentHost()->uuid());
|
||||
params.insert("thingId", thingId);
|
||||
params.insert("stateTypeId", stateTypeId);
|
||||
params.insert("value", value);
|
||||
sendNotification("ThingStateChanged", params);
|
||||
});
|
||||
|
||||
connect(engine->thingManager(), &DeviceManager::fetchingDataChanged, [=]() {
|
||||
qDebug() << "Fetching data changed";
|
||||
QVariantMap params;
|
||||
params.insert("nymeaId", engine->jsonRpcClient()->currentHost()->uuid());
|
||||
params.insert("isReady", !engine->thingManager()->fetchingData());
|
||||
qDebug() << "Nymea host is ready" << engine->jsonRpcClient()->currentHost()->uuid();
|
||||
sendNotification("ReadyStateChanged", params);
|
||||
});
|
||||
}
|
||||
|
||||
qDebug() << "NymeaAppService started.";
|
||||
|
||||
}
|
||||
|
||||
QHash<QUuid, Engine *> NymeaAppService::engines() const
|
||||
{
|
||||
return m_engines;
|
||||
}
|
||||
|
||||
void NymeaAppService::sendNotification(const QString ¬ification, const QVariantMap ¶ms)
|
||||
{
|
||||
QVariantMap data;
|
||||
data.insert("notification", notification);
|
||||
data.insert("params", params);
|
||||
QString payload = QJsonDocument::fromVariant(data).toJson();
|
||||
QtAndroid::androidService().callMethod<void>("sendBroadcast",
|
||||
"(Ljava/lang/String;)V",
|
||||
QAndroidJniObject::fromString(payload).object<jstring>());
|
||||
|
||||
}
|
||||
25
androidservice/nymeaappservice/nymeaappservice.h
Normal file
25
androidservice/nymeaappservice/nymeaappservice.h
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef NYMEAAPPSERVICE_H
|
||||
#define NYMEAAPPSERVICE_H
|
||||
|
||||
#include <QAndroidService>
|
||||
|
||||
#include "engine.h"
|
||||
|
||||
class NymeaAppService : public QAndroidService
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NymeaAppService(int argc, char** argv);
|
||||
|
||||
QHash<QUuid, Engine*> engines() const;
|
||||
|
||||
private:
|
||||
void sendNotification(const QString ¬ification, const QVariantMap ¶ms);
|
||||
|
||||
|
||||
private:
|
||||
QHash<QUuid, Engine*> m_engines;
|
||||
|
||||
};
|
||||
|
||||
#endif // NYMEAAPPSERVICE_H
|
||||
37
androidservice/service_main.cpp
Normal file
37
androidservice/service_main.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include <QDebug>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "nymeaappservice/nymeaappservice.h"
|
||||
#include "controlviews/devicecontrolapplication.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
qWarning() << "Service starting from a separate .so file";
|
||||
|
||||
QLoggingCategory::setFilterRules("qt.remoteobjects.debug=true\n");
|
||||
|
||||
QStringList args;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
args.append(QByteArray(argv[i]));
|
||||
qDebug() << "nymea-app: Added command line arg" << args.last();
|
||||
}
|
||||
QCommandLineParser parser;
|
||||
QCommandLineOption controlActivityOption("controlActivity");
|
||||
parser.addOption(controlActivityOption);
|
||||
parser.parse(args);
|
||||
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
|
||||
QCoreApplication *app;
|
||||
if (parser.isSet(controlActivityOption)) {
|
||||
qDebug() << "nymea-app: Starting Device Control Activity";
|
||||
app = new DeviceControlApplication(argc, argv);
|
||||
} else {
|
||||
qDebug() << "nymea-app: Starting NymeaAppService background service";
|
||||
app = new NymeaAppService(argc, argv);
|
||||
}
|
||||
return app->exec();
|
||||
}
|
||||
@ -11,3 +11,6 @@ APP_REVISION=$$member(VERSION_INFO, 1)
|
||||
|
||||
DEFINES+=APP_VERSION=\\\"$${APP_VERSION}\\\"
|
||||
android:QMAKE_POST_LINK += cp $$top_srcdir/version.txt $$top_builddir/
|
||||
|
||||
DISTFILES += \
|
||||
$$PWD/packaging/android/src/io/guh/nymeaapp/NymeaHost.java
|
||||
|
||||
@ -175,10 +175,11 @@ void DeviceManager::notificationReceived(const QVariantMap &data)
|
||||
qWarning() << "Device state change notification received for an unknown device";
|
||||
return;
|
||||
}
|
||||
QUuid stateTyoeId = data.value("params").toMap().value("stateTypeId").toUuid();
|
||||
QUuid stateTypeId = data.value("params").toMap().value("stateTypeId").toUuid();
|
||||
QVariant value = data.value("params").toMap().value("value");
|
||||
// qDebug() << "Device state changed for:" << dev->name() << "State name:" << dev->thingClass()->stateTypes()->getStateType(stateTyoeId) << "value:" << value;
|
||||
dev->setStateValue(stateTyoeId, value);
|
||||
// qDebug() << "Device state changed for:" << dev->name() << "State name:" << dev->thingClass()->stateTypes()->getStateType(stateTypeId) << "value:" << value;
|
||||
dev->setStateValue(stateTypeId, value);
|
||||
emit thingStateChanged(dev->id(), stateTypeId, value);
|
||||
} else if (notification == "Devices.DeviceAdded") {
|
||||
Device *dev = JsonTypes::unpackDevice(this, data.value("params").toMap().value("device").toMap(), m_thingClasses);
|
||||
if (!dev) {
|
||||
|
||||
@ -61,6 +61,7 @@ class DeviceManager : public JsonHandler
|
||||
|
||||
Q_PROPERTY(bool fetchingData READ fetchingData NOTIFY fetchingDataChanged)
|
||||
|
||||
Q_ENUMS(RemovePolicy)
|
||||
public:
|
||||
enum RemovePolicy {
|
||||
RemovePolicyNone,
|
||||
@ -151,7 +152,8 @@ signals:
|
||||
void fetchingDataChanged();
|
||||
void notificationReceived(const QString &deviceId, const QString &eventTypeId, const QVariantList ¶ms);
|
||||
|
||||
void eventTriggered(const QString &deviceId, const QString &eventTypeId, const QVariantMap params);
|
||||
void eventTriggered(const QUuid &deviceId, const QUuid &eventTypeId, const QVariantMap params);
|
||||
void thingStateChanged(const QUuid &deviceId, const QUuid &stateTypeId, const QVariant &value);
|
||||
|
||||
private:
|
||||
Vendors *m_vendors;
|
||||
|
||||
@ -61,9 +61,6 @@ class Engine : public QObject
|
||||
public:
|
||||
explicit Engine(QObject *parent = nullptr);
|
||||
|
||||
bool connected() const;
|
||||
QString connectedHost() const;
|
||||
|
||||
DeviceManager *deviceManager() const;
|
||||
DeviceManager *thingManager() const;
|
||||
RuleManager *ruleManager() const;
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
#if defined(LIBNYMEA_COMMON)
|
||||
# define LIBNYMEA_COMMON_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
# define LIBNYMEA_COMMON_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
@ -1,15 +0,0 @@
|
||||
include(../config.pri)
|
||||
|
||||
TARGET = nymea-common
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib
|
||||
|
||||
QT -= gui
|
||||
QT += network
|
||||
|
||||
HEADERS += \
|
||||
|
||||
|
||||
SOURCES += \
|
||||
|
||||
|
||||
@ -82,6 +82,12 @@ icons.path = /usr/share/
|
||||
INSTALLS += desktopfile icons
|
||||
}
|
||||
|
||||
# Android service
|
||||
android: {
|
||||
SUBDIRS += androidservice
|
||||
androidservice.depends = libnymea-app
|
||||
}
|
||||
|
||||
# Linux desktop (snap package)
|
||||
snap: {
|
||||
desktopfile.files = packaging/linux/nymea-app.desktop
|
||||
|
||||
@ -81,6 +81,8 @@ android {
|
||||
|
||||
# https://bugreports.qt.io/browse/QTBUG-83165
|
||||
LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH}
|
||||
|
||||
ANDROID_ABIS = armeabi-v7a arm64-v8a
|
||||
}
|
||||
|
||||
macx: {
|
||||
@ -160,4 +162,3 @@ BR=$$BRANDING
|
||||
|
||||
target.path = /usr/bin
|
||||
INSTALLS += target
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
#include <QAndroidJniObject>
|
||||
#include <QtAndroid>
|
||||
#include <QDebug>
|
||||
#include <QAndroidIntent>
|
||||
|
||||
|
||||
// WindowManager.LayoutParams
|
||||
@ -47,13 +48,16 @@ static PlatformHelperAndroid *m_instance;
|
||||
static QAndroidJniObject getAndroidWindow()
|
||||
{
|
||||
QAndroidJniObject window = QtAndroid::androidActivity().callObjectMethod("getWindow", "()Landroid/view/Window;");
|
||||
window.callMethod<void>("addFlags", "(I)V", FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
window.callMethod<void>("clearFlags", "(I)V", FLAG_TRANSLUCENT_STATUS);
|
||||
return window;
|
||||
}
|
||||
|
||||
PlatformHelperAndroid::PlatformHelperAndroid(QObject *parent) : PlatformHelper(parent)
|
||||
{
|
||||
// QAndroidIntent serviceIntent(QtAndroid::androidActivity().object(), "io.guh.nymeaapp.NymeaAppControlService");
|
||||
|
||||
// m_serviceConnection = new DeviceControlServiceConnection();
|
||||
// QtAndroid::bindService(serviceIntent, *m_serviceConnection, QtAndroid::BindFlag::AutoCreate);
|
||||
|
||||
m_instance = this;
|
||||
}
|
||||
|
||||
@ -123,6 +127,33 @@ void PlatformHelperAndroid::vibrate(PlatformHelper::HapticsFeedback feedbackType
|
||||
QtAndroid::androidActivity().callMethod<void>("vibrate","(I)V", duration);
|
||||
}
|
||||
|
||||
//void PlatformHelperAndroid::syncThings()
|
||||
//{
|
||||
|
||||
// QAndroidIntent serviceIntent(QtAndroid::androidActivity().object(),
|
||||
// "io/guh/nymeaapp/NymeaAppService");
|
||||
// QAndroidJniObject result = QtAndroid::androidActivity().callObjectMethod(
|
||||
// "startService",
|
||||
// "(Landroid/content/Intent;)Landroid/content/ComponentName;",
|
||||
// serviceIntent.handle().object());
|
||||
|
||||
|
||||
//// QtAndroid::androidService()
|
||||
|
||||
//// QAndroidIntent serviceIntent(QtAndroid::androidActivity().object(),
|
||||
//// "io/guh/nymeaapp/NymeaAppControlService");
|
||||
//// serviceIntent.putExtra("name", QByteArray("foobar"));
|
||||
|
||||
|
||||
//// m_serviceConnection->handle().callMethod<void>("syncThings", "(Ljava/lang/String;)V", "bla");
|
||||
|
||||
|
||||
//// QAndroidJniObject result = QtAndroid::androidActivity().callObjectMethod(
|
||||
//// "syncThings",
|
||||
//// "(Landroid/content/Intent;)Landroid/content/ComponentName;",
|
||||
//// m_serviceConnection->handle().object());
|
||||
//}
|
||||
|
||||
void PlatformHelperAndroid::setTopPanelColor(const QColor &color)
|
||||
{
|
||||
PlatformHelper::setTopPanelColor(color);
|
||||
@ -132,6 +163,8 @@ void PlatformHelperAndroid::setTopPanelColor(const QColor &color)
|
||||
|
||||
QtAndroid::runOnAndroidThread([=]() {
|
||||
QAndroidJniObject window = getAndroidWindow();
|
||||
window.callMethod<void>("addFlags", "(I)V", FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
window.callMethod<void>("clearFlags", "(I)V", FLAG_TRANSLUCENT_STATUS);
|
||||
window.callMethod<void>("setStatusBarColor", "(I)V", color.rgba());
|
||||
});
|
||||
|
||||
|
||||
@ -31,9 +31,11 @@
|
||||
#ifndef PLATFORMHELPERANDROID_H
|
||||
#define PLATFORMHELPERANDROID_H
|
||||
|
||||
#include <QObject>
|
||||
#include "platformhelper.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QtAndroid>
|
||||
#include <QAndroidServiceConnection>
|
||||
|
||||
class PlatformHelperAndroid : public PlatformHelper
|
||||
{
|
||||
@ -62,7 +64,6 @@ public:
|
||||
|
||||
private:
|
||||
static void permissionRequestFinished(const QtAndroid::PermissionResultMap &);
|
||||
|
||||
};
|
||||
|
||||
#endif // PLATFORMHELPERANDROID_H
|
||||
|
||||
@ -456,52 +456,6 @@ ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
function interfaceListToDevicePage(interfaceList) {
|
||||
var page;
|
||||
if (interfaceList.indexOf("media") >= 0) {
|
||||
page = "MediaDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("button") >= 0) {
|
||||
page = "ButtonDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("powerswitch") >= 0) {
|
||||
page = "ButtonDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("weather") >= 0) {
|
||||
page = "WeatherDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("heating") >= 0 || interfaceList.indexOf("thermostat") >= 0) {
|
||||
page = "HeatingDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("sensor") >= 0) {
|
||||
page = "SensorDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("inputtrigger") >= 0) {
|
||||
page = "InputTriggerDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("garagedoor") >= 0 ) {
|
||||
page = "GarageThingPage.qml";
|
||||
} else if (interfaceList.indexOf("light") >= 0) {
|
||||
page = "LightDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("shutter") >= 0 || interfaceList.indexOf("blind") >= 0) {
|
||||
page = "ShutterDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("awning") >= 0) {
|
||||
page = "AwningDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("notifications") >= 0) {
|
||||
page = "NotificationsDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("fingerprintreader") >= 0) {
|
||||
page = "FingerprintReaderDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("smartmeter") >= 0) {
|
||||
page = "SmartMeterDevicePage.qml"
|
||||
} else if (interfaceList.indexOf("powersocket") >= 0) {
|
||||
page = "PowersocketDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("doorbell") >= 0) {
|
||||
page = "DoorbellDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("irrigation") >= 0) {
|
||||
page = "IrrigationDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("ventilation") >= 0) {
|
||||
page = "VentilationDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("barcodescanner") >= 0) {
|
||||
page = "BarcodeScannerThingPage.qml";
|
||||
} else {
|
||||
page = "GenericDevicePage.qml";
|
||||
}
|
||||
print("Selecting page", page, "for interface list:", interfaceList)
|
||||
return page;
|
||||
}
|
||||
|
||||
function pad(num, size) {
|
||||
var s = "000000000" + num;
|
||||
|
||||
@ -79,7 +79,7 @@ DevicePageBase {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
property string currentImage: {
|
||||
if (root.isExtended) {
|
||||
return app.pad(Math.round(root.percentageState.value / 10), 2) + "0"
|
||||
return NymeaUtils.pad(Math.round(root.percentageState.value / 10), 2) + "0"
|
||||
}
|
||||
if (root.intermediatePositionStateType) {
|
||||
return root.stateState.value === "closed" ? "100"
|
||||
|
||||
@ -129,7 +129,7 @@ Page {
|
||||
|
||||
device: devicesInGroup.get(index)
|
||||
|
||||
onClicked: pageStack.push(Qt.resolvedUrl("../devicepages/" + app.interfaceListToDevicePage(deviceClass.interfaces)), {device: device})
|
||||
onClicked: pageStack.push(Qt.resolvedUrl("../devicepages/" + NymeaUtils.interfaceListToDevicePage(deviceClass.interfaces)), {device: device})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ MainViewBase {
|
||||
height: gridView.cellHeight
|
||||
device: engine.deviceManager.devices.getDevice(deviceId)
|
||||
|
||||
onClicked: pageStack.push(Qt.resolvedUrl("../devicepages/" + app.interfaceListToDevicePage(deviceClass.interfaces)), {device: device})
|
||||
onClicked: pageStack.push(Qt.resolvedUrl("../devicepages/" + NymeaUtils.interfaceListToDevicePage(deviceClass.interfaces)), {device: device})
|
||||
|
||||
onPressAndHold: root.editMode = true
|
||||
|
||||
|
||||
@ -15,4 +15,53 @@ Item {
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function interfaceListToDevicePage(interfaceList) {
|
||||
print("**** getting page for interfaces", interfaceList)
|
||||
var page;
|
||||
if (interfaceList.indexOf("media") >= 0) {
|
||||
page = "MediaDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("button") >= 0) {
|
||||
page = "ButtonDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("powerswitch") >= 0) {
|
||||
page = "ButtonDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("weather") >= 0) {
|
||||
page = "WeatherDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("heating") >= 0 || interfaceList.indexOf("thermostat") >= 0) {
|
||||
page = "HeatingDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("sensor") >= 0) {
|
||||
page = "SensorDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("inputtrigger") >= 0) {
|
||||
page = "InputTriggerDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("garagedoor") >= 0 ) {
|
||||
page = "GarageThingPage.qml";
|
||||
} else if (interfaceList.indexOf("light") >= 0) {
|
||||
page = "LightDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("shutter") >= 0 || interfaceList.indexOf("blind") >= 0) {
|
||||
page = "ShutterDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("awning") >= 0) {
|
||||
page = "AwningDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("notifications") >= 0) {
|
||||
page = "NotificationsDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("fingerprintreader") >= 0) {
|
||||
page = "FingerprintReaderDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("smartmeter") >= 0) {
|
||||
page = "SmartMeterDevicePage.qml"
|
||||
} else if (interfaceList.indexOf("powersocket") >= 0) {
|
||||
page = "PowersocketDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("doorbell") >= 0) {
|
||||
page = "DoorbellDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("irrigation") >= 0) {
|
||||
page = "IrrigationDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("ventilation") >= 0) {
|
||||
page = "VentilationDevicePage.qml";
|
||||
} else if (interfaceList.indexOf("barcodescanner") >= 0) {
|
||||
page = "BarcodeScannerThingPage.qml";
|
||||
} else {
|
||||
page = "GenericDevicePage.qml";
|
||||
}
|
||||
print("Selecting page", page, "for interface list:", interfaceList)
|
||||
return page;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -62,8 +62,61 @@
|
||||
<!-- extract android style -->
|
||||
</activity>
|
||||
|
||||
|
||||
<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">
|
||||
<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"/>
|
||||
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
|
||||
<!-- Deploy Qt libs as part of package -->
|
||||
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
|
||||
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
|
||||
<!-- Run with local libs -->
|
||||
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
|
||||
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
|
||||
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
|
||||
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
|
||||
<!-- Messages maps -->
|
||||
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
|
||||
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
|
||||
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
|
||||
<!-- Messages maps -->
|
||||
<meta-data android:name="android.app.extract_android_style" android:value="minimal"/>
|
||||
<!-- Splash screen -->
|
||||
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splash"/>
|
||||
<meta-data android:name="android.app.splash_screen_sticky" android:value="true"/>
|
||||
<!-- Splash screen -->
|
||||
</activity>
|
||||
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
|
||||
<service android:process=":qt_service" android:name="io.guh.nymeaapp.NymeaAppService">
|
||||
<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"/>
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
|
||||
|
||||
<!-- Deploy Qt libs as part of package -->
|
||||
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
|
||||
|
||||
<!-- Run with local libs -->
|
||||
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
|
||||
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
|
||||
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
|
||||
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
|
||||
<!-- Run with local libs -->
|
||||
|
||||
<!-- Background running -->
|
||||
<meta-data android:name="android.app.background_running" android:value="true"/>
|
||||
<!-- Background running -->
|
||||
</service>
|
||||
|
||||
<service android:name="com.google.firebase.messaging.MessageForwardingService" android:exported="false">
|
||||
</service>
|
||||
|
||||
@ -73,9 +126,15 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name="io.guh.nymeaapp.NymeaAppControlService" android:permission="android.permission.BIND_CONTROLS" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.controls.ControlsProviderService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
||||
|
||||
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
|
||||
|
||||
@ -30,6 +30,8 @@ dependencies {
|
||||
compile 'com.google.android.gms:play-services-base:15.0.1'
|
||||
compile 'com.google.firebase:firebase-messaging:18.0.0'
|
||||
compile 'com.google.firebase.messaging.cpp:firebase_messaging_cpp@aar'
|
||||
implementation 'org.reactivestreams:reactive-streams:1.0.3'
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
||||
9
packaging/android/src/io/guh/nymeaapp/Action.java
Normal file
9
packaging/android/src/io/guh/nymeaapp/Action.java
Normal file
@ -0,0 +1,9 @@
|
||||
package io.guh.nymeaapp;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class Action {
|
||||
public UUID typeId;
|
||||
public String name;
|
||||
public String displayName;
|
||||
}
|
||||
@ -8,9 +8,6 @@ import android.telephony.TelephonyManager;
|
||||
import android.provider.Settings.Secure;
|
||||
import android.os.Vibrator;
|
||||
|
||||
//import com.google.firebase.messaging.MessageForwardingService;
|
||||
|
||||
|
||||
public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActivity
|
||||
{
|
||||
public String deviceSerial()
|
||||
|
||||
@ -0,0 +1,286 @@
|
||||
package io.guh.nymeaapp;
|
||||
|
||||
import android.util.Log;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.ComponentName;
|
||||
import android.app.PendingIntent;
|
||||
import android.net.Uri;
|
||||
import android.content.Context;
|
||||
import android.service.controls.ControlsProviderService;
|
||||
import android.service.controls.actions.*;
|
||||
import android.service.controls.Control;
|
||||
import android.service.controls.DeviceTypes;
|
||||
import android.service.controls.templates.*;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
|
||||
import java.util.concurrent.Flow.Publisher;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
import java.util.HashMap;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.processors.ReplayProcessor;
|
||||
import org.reactivestreams.FlowAdapters;
|
||||
import org.json.*;
|
||||
|
||||
// Android device controls service
|
||||
|
||||
// This service is instantiated by the android device controls on demand. It will
|
||||
// connect to the NymeaAppService and interact with nymea through that.
|
||||
|
||||
public class NymeaAppControlService extends ControlsProviderService {
|
||||
private String TAG = "nymea-app: NymeaAppControlService";
|
||||
private NymeaAppServiceConnection m_serviceConnection;
|
||||
|
||||
// For publishing all available
|
||||
private ReplayProcessor m_publisherForAll;
|
||||
private ArrayList<UUID> m_pendingForAll = new ArrayList<UUID>(); // pending nymea ids to query
|
||||
|
||||
private ReplayProcessor m_updatePublisher;
|
||||
private List m_activeControlIds;
|
||||
|
||||
|
||||
private void ensureServiceConnection() {
|
||||
if (m_serviceConnection == null) {
|
||||
m_serviceConnection = new NymeaAppServiceConnection(getBaseContext()) {
|
||||
@Override public void onConnectedChanged(boolean connected) {
|
||||
Log.d(TAG, "Connected to NymeaAppService. Known hosts: " + m_serviceConnection.getHosts().size());
|
||||
if (connected && m_publisherForAll != null) {
|
||||
Log.d(TAG, "Processing all");
|
||||
processAll();
|
||||
}
|
||||
}
|
||||
@Override public void onReadyChanged(UUID nymeaId, boolean ready) {
|
||||
Log.d(TAG, "Nymea instance " + nymeaId.toString() + " ready state changed: " + Boolean.toString(ready));
|
||||
if (ready) {
|
||||
process(nymeaId);
|
||||
}
|
||||
}
|
||||
@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);
|
||||
m_updatePublisher.onNext(thingToControl(nymeaId, thingId));
|
||||
// m_updatePublisher.onComplete();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Intent serviceIntent = new Intent(this, NymeaAppService.class);
|
||||
bindService(serviceIntent, m_serviceConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
private void processAll() {
|
||||
ensureServiceConnection();
|
||||
if (m_serviceConnection.connected()) {
|
||||
// Need to add all the pending before processing
|
||||
if (m_publisherForAll != null) {
|
||||
for (UUID nymeaId: m_serviceConnection.getHosts().keySet()) {
|
||||
m_pendingForAll.add(nymeaId);
|
||||
}
|
||||
}
|
||||
for (UUID nymeaId: m_serviceConnection.getHosts().keySet()) {
|
||||
process(nymeaId);
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Not connected to NymeaAppService yet...");
|
||||
}
|
||||
}
|
||||
|
||||
private void process(UUID nymeaId) {
|
||||
Log.d(TAG, "Processing...");
|
||||
ensureServiceConnection();
|
||||
if (!m_serviceConnection.connected()) {
|
||||
Log.d(TAG, "NymeaAppService not connected to nymea instance " + nymeaId + " yet.");
|
||||
return;
|
||||
}
|
||||
if (!m_serviceConnection.getHosts().keySet().contains(nymeaId)) {
|
||||
Log.d(TAG, "Service connection is not ready yet...");
|
||||
return;
|
||||
}
|
||||
|
||||
for (Thing thing : m_serviceConnection.getHosts().get(nymeaId).things.values()) {
|
||||
Log.d(TAG, "Processing thing: " + thing.name);
|
||||
|
||||
if (m_publisherForAll != null) {
|
||||
Log.d(TAG, "Adding stateless");
|
||||
m_publisherForAll.onNext(thingToControl(nymeaId, thing.id));
|
||||
}
|
||||
|
||||
if (m_updatePublisher != null) {
|
||||
if (m_activeControlIds.contains(thing.id.toString())) {
|
||||
Log.d(TAG, "Adding stateful");
|
||||
m_updatePublisher.onNext(thingToControl(nymeaId, thing.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pendingForAll.contains(nymeaId)) {
|
||||
m_pendingForAll.remove(nymeaId);
|
||||
}
|
||||
|
||||
// The publisher for all needs to be completed when done
|
||||
if (m_publisherForAll != null && m_pendingForAll.isEmpty()) {
|
||||
Log.d(TAG, "Completing all publisher");
|
||||
m_publisherForAll.onComplete();
|
||||
}
|
||||
|
||||
Log.d(TAG, "Done processing");
|
||||
// We never close the update publisher as we need that one to send updates
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Publisher createPublisherForAllAvailable() {
|
||||
Log.d(TAG, "Creating publishers for all");
|
||||
m_publisherForAll = ReplayProcessor.create();
|
||||
processAll();
|
||||
return FlowAdapters.toFlowPublisher(m_publisherForAll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Publisher createPublisherFor(List controlIds) {
|
||||
Log.d(TAG, "Creating publishers for " + Integer.toString(controlIds.size()));
|
||||
m_updatePublisher = ReplayProcessor.create();
|
||||
m_activeControlIds = controlIds;
|
||||
processAll();
|
||||
return FlowAdapters.toFlowPublisher(m_updatePublisher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performControlAction(String controlId, ControlAction action, Consumer consumer) {
|
||||
Log.d(TAG, "Performing control action: " + controlId);
|
||||
|
||||
UUID nymeaId = m_serviceConnection.hostForThing(UUID.fromString(controlId));
|
||||
if (nymeaId == null) {
|
||||
Log.d(TAG, "Nymea host not found for thing id: " + controlId);
|
||||
consumer.accept(ControlAction.RESPONSE_FAIL);
|
||||
return;
|
||||
}
|
||||
Thing thing = m_serviceConnection.getThing(UUID.fromString(controlId));
|
||||
if (thing == null) {
|
||||
Log.d(TAG, "Thing not found for id: " + controlId);
|
||||
consumer.accept(ControlAction.RESPONSE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
UUID actionTypeId;
|
||||
String param;
|
||||
if (thing.interfaces.contains("dimmablelight") && action instanceof FloatAction) {
|
||||
actionTypeId = thing.stateByName("brightness").typeId;
|
||||
FloatAction fAction = (FloatAction) action;
|
||||
param = String.valueOf(Math.round(fAction.getNewValue()));
|
||||
} else if (thing.interfaces.contains("power") && action instanceof BooleanAction) {
|
||||
actionTypeId = thing.stateByName("power").typeId;
|
||||
BooleanAction bAction = (BooleanAction) action;
|
||||
param = bAction.getNewState() == true ? "true" : "false";
|
||||
} else if (thing.interfaces.contains("closable") && action instanceof BooleanAction) {
|
||||
BooleanAction bAction = (BooleanAction) action;
|
||||
if (bAction.getNewState()) {
|
||||
Log.d(TAG, "executing open");
|
||||
actionTypeId = thing.actionByName("open").typeId;
|
||||
} else {
|
||||
Log.d(TAG, "executing close");
|
||||
actionTypeId = thing.actionByName("close").typeId;
|
||||
}
|
||||
param = "";
|
||||
} else if (thing.interfaces.contains("extendedvolumecontroller")) {
|
||||
actionTypeId = thing.stateByName("volume").typeId;
|
||||
FloatAction fAction = (FloatAction) action;
|
||||
param = String.valueOf(Math.round(fAction.getNewValue()));
|
||||
} else {
|
||||
Log.d(TAG, "Unhandled action for: " + thing.name);
|
||||
consumer.accept(ControlAction.RESPONSE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
m_serviceConnection.executeAction(nymeaId, thing.id, actionTypeId, param);
|
||||
consumer.accept(ControlAction.RESPONSE_OK);
|
||||
|
||||
}
|
||||
|
||||
private HashMap<UUID, Integer> m_intents = new HashMap<UUID, Integer>();
|
||||
|
||||
private Control thingToControl(UUID nymeaId, UUID thingId) {
|
||||
// Log.d(TAG, "Creating control for thing: " + thing.name + " id: " + thing.id);
|
||||
|
||||
NymeaHost nymeaHost = m_serviceConnection.getHosts().get(nymeaId);
|
||||
Thing thing = nymeaHost.things.get(thingId);
|
||||
|
||||
// NOTE: intentId 1 doesn't work for some reason I don't understand yet...
|
||||
// so let's make sure we never add "1" to it by always added 100
|
||||
int intentId = m_intents.size() + 100;
|
||||
PendingIntent pi;
|
||||
if (m_intents.containsKey(thing.id)) {
|
||||
intentId = m_intents.get(thing.id);
|
||||
} else {
|
||||
m_intents.put(thing.id, intentId);
|
||||
}
|
||||
|
||||
Context context = getBaseContext();
|
||||
Intent intent = new Intent(context, NymeaAppControlsActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||
intent.putExtra("nymeaId", nymeaId.toString());
|
||||
intent.putExtra("thingId", thing.id.toString());
|
||||
pi = PendingIntent.getActivity(context, intentId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
Log.d(TAG, "Created pendingintent for " + thing.name + " with id " + intentId + " and extra " + thing.id);
|
||||
|
||||
Control.StatefulBuilder builder = new Control.StatefulBuilder(thing.id.toString(), pi)
|
||||
.setTitle(thing.name)
|
||||
.setSubtitle(thing.className)
|
||||
.setStructure(nymeaHost.name);
|
||||
|
||||
if (thing.interfaces.contains("impulsebasedgaragedoor")) {
|
||||
builder.setDeviceType(DeviceTypes.TYPE_GARAGE);
|
||||
builder.setControlTemplate(new StatelessTemplate(thing.id.toString()));
|
||||
} else if (thing.interfaces.contains("statefulgaragedoor")) {
|
||||
builder.setDeviceType(DeviceTypes.TYPE_GARAGE);
|
||||
State stateState = thing.stateByName("state");
|
||||
ControlButton controlButton = new ControlButton(stateState.value.equals("open"), stateState.displayName);
|
||||
builder.setControlTemplate(new ToggleTemplate(thing.id.toString(), controlButton));
|
||||
|
||||
// } else if (thing.interfaces.contains("extendedstatefulgaragedoor")) {
|
||||
// builder.setDeviceTyoe(DeviceTypes.TYPE_GARAGE);
|
||||
|
||||
} else if (thing.interfaces.contains("light")) {
|
||||
builder.setDeviceType(DeviceTypes.TYPE_LIGHT);
|
||||
State powerState = thing.stateByName("power");
|
||||
ControlButton controlButton = new ControlButton(powerState.value.equals("true"), powerState.displayName);
|
||||
|
||||
if (thing.interfaces.contains("dimmablelight")) {
|
||||
State brightnessState = thing.stateByName("brightness");
|
||||
RangeTemplate rangeTemplate = new RangeTemplate(thing.id.toString(), 0, 100, Float.parseFloat(brightnessState.value), 1, brightnessState.displayName);
|
||||
builder.setControlTemplate(new ToggleRangeTemplate(thing.id.toString(), controlButton, rangeTemplate));
|
||||
} else {
|
||||
builder.setControlTemplate(new ToggleTemplate(thing.id.toString(), controlButton));
|
||||
}
|
||||
} else if (thing.interfaces.contains("powersocket")) {
|
||||
builder.setDeviceType(DeviceTypes.TYPE_OUTLET);
|
||||
State powerState = thing.stateByName("power");
|
||||
ControlButton controlButton = new ControlButton(powerState.value.equals("true"), powerState.displayName);
|
||||
builder.setControlTemplate(new ToggleTemplate(thing.id.toString(), controlButton));
|
||||
} else if (thing.interfaces.contains("mediaplayer")) {
|
||||
if (thing.stateByName("playerType").value == "video") {
|
||||
builder.setDeviceType(DeviceTypes.TYPE_TV);
|
||||
} else {
|
||||
// FIXME: There doesn't seem to be a speaker DeviceType!?!
|
||||
builder.setDeviceType(DeviceTypes.TYPE_TV);
|
||||
}
|
||||
if (thing.interfaces.contains("extendedvolumecontroller")) {
|
||||
State volumeState = thing.stateByName("volume");
|
||||
RangeTemplate rangeTemplate = new RangeTemplate(thing.id.toString(), 0, 100, Float.parseFloat(volumeState.value), 1, volumeState.displayName);
|
||||
builder.setControlTemplate(rangeTemplate);
|
||||
}
|
||||
} else {
|
||||
builder.setDeviceType(DeviceTypes.TYPE_GENERIC_ON_OFF);
|
||||
}
|
||||
builder.setStatus(Control.STATUS_OK);
|
||||
|
||||
// Log.d(TAG, "Created control for thing: " + thing.name + " id: " + thing.id);
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package io.guh.nymeaapp;
|
||||
import android.util.Log;
|
||||
import android.content.Intent;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Build;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.provider.Settings.Secure;
|
||||
import android.os.Vibrator;
|
||||
import android.os.Process;
|
||||
|
||||
// An activity spawned by android device controls on demand.
|
||||
|
||||
public class NymeaAppControlsActivity extends org.qtproject.qt5.android.bindings.QtActivity
|
||||
{
|
||||
private static final String TAG = "nymea-app: NymeaAppControlActivity";
|
||||
|
||||
|
||||
@Override public void onPause() {
|
||||
Log.d(TAG, "Pausing...");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Override public void onResume() {
|
||||
super.onResume();
|
||||
Log.d(TAG, "Resuming...");
|
||||
}
|
||||
|
||||
|
||||
@Override public void onDestroy() {
|
||||
Log.d(TAG, "Destroying...");
|
||||
}
|
||||
|
||||
|
||||
public String nymeaId()
|
||||
{
|
||||
return getIntent().getStringExtra("nymeaId");
|
||||
}
|
||||
|
||||
public String thingId()
|
||||
{
|
||||
return getIntent().getStringExtra("thingId");
|
||||
}
|
||||
|
||||
public void vibrate(int duration)
|
||||
{
|
||||
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
|
||||
v.vibrate(duration);
|
||||
}
|
||||
|
||||
}
|
||||
50
packaging/android/src/io/guh/nymeaapp/NymeaAppService.java
Normal file
50
packaging/android/src/io/guh/nymeaapp/NymeaAppService.java
Normal file
@ -0,0 +1,50 @@
|
||||
package io.guh.nymeaapp;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import org.qtproject.qt5.android.bindings.QtService;
|
||||
|
||||
// Background service establishing a connection to nymea and providing data on android specific interfaces
|
||||
// such as IBinder and BroadcastListener
|
||||
|
||||
// This service loads the service_main Qt entry point and does most of its work in C++/Qt
|
||||
|
||||
public class NymeaAppService extends QtService
|
||||
{
|
||||
public static final String NYMEA_APP_BROADCAST = "io.guh.nymeaapp.NymeaAppService.broadcast";
|
||||
|
||||
private static final String TAG = "nymea-app: NymeaAppService";
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.i(TAG, "Creating Service");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
Log.i(TAG, "Destroying Service");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
int ret = super.onStartCommand(intent, flags, startId);
|
||||
|
||||
// Do some work
|
||||
|
||||
Log.d(TAG, "*************** Service started");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void sendBroadcast(String payload) {
|
||||
Intent sendToUiIntent = new Intent();
|
||||
sendToUiIntent.setAction(NYMEA_APP_BROADCAST);
|
||||
sendToUiIntent.putExtra("data", payload);
|
||||
// Log.d(TAG, "Service sending broadcast");
|
||||
sendBroadcast(sendToUiIntent);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,278 @@
|
||||
package io.guh.nymeaapp;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
import java.util.HashMap;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
|
||||
import android.service.controls.Control;
|
||||
import android.service.controls.DeviceTypes;
|
||||
|
||||
import io.reactivex.processors.ReplayProcessor;
|
||||
|
||||
import org.json.*;
|
||||
|
||||
// Helper class to establish a connection to the NymeaAppService and interact
|
||||
// with that using IBinder and ServiceBroadcastListener
|
||||
|
||||
public class NymeaAppServiceConnection implements ServiceConnection {
|
||||
private static final String TAG = "nymea-app: NymeaAppServiceConnection";
|
||||
private IBinder m_service;
|
||||
private Context m_context;
|
||||
|
||||
private boolean m_connected = false;
|
||||
private HashMap<UUID, NymeaHost> m_nymeaHosts = new HashMap<UUID, NymeaHost>();
|
||||
|
||||
public NymeaAppServiceConnection(Context context) {
|
||||
super();
|
||||
m_context = context;
|
||||
}
|
||||
|
||||
final public boolean connected() {
|
||||
return m_connected;
|
||||
}
|
||||
public void onConnectedChanged(boolean connected) {};
|
||||
|
||||
public final HashMap<UUID, NymeaHost> getHosts() {
|
||||
return m_nymeaHosts;
|
||||
}
|
||||
|
||||
final public Thing getThing(UUID thingId) {
|
||||
for (HashMap.Entry<UUID, NymeaHost> entry : m_nymeaHosts.entrySet()) {
|
||||
Thing thing = entry.getValue().things.get(thingId);
|
||||
if (thing != null) {
|
||||
return thing;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
final public UUID hostForThing(UUID thingId) {
|
||||
for (HashMap.Entry<UUID, NymeaHost> entry : m_nymeaHosts.entrySet()) {
|
||||
Thing thing = entry.getValue().things.get(thingId);
|
||||
if (thing != null) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void onReadyChanged(UUID nymeaId, boolean ready) {}
|
||||
public void onError() {}
|
||||
public void onUpdate(UUID nymeaId, UUID thingId) {}
|
||||
|
||||
final public void executeAction(UUID nymeaId, UUID thingId, UUID actionTypeId, String paramValue) {
|
||||
try {
|
||||
JSONObject params = new JSONObject();
|
||||
params.put("nymeaId", nymeaId.toString());
|
||||
params.put("thingId", thingId.toString());
|
||||
params.put("actionTypeId", actionTypeId.toString());
|
||||
JSONArray actionParams = new JSONArray();
|
||||
JSONObject param = new JSONObject();
|
||||
param.put("paramTypeId", actionTypeId.toString());
|
||||
param.put("value", paramValue);
|
||||
actionParams.put(param);
|
||||
params.put("params", actionParams);
|
||||
Parcel parcel = createRequest("ExecuteAction", params);
|
||||
Parcel retParcel = Parcel.obtain();
|
||||
m_service.transact(1, parcel, retParcel, 0);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Error calling executeAction on NymeaAppService");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
Log.d(TAG, "Connected to NymeaAppService");
|
||||
m_service = service;
|
||||
|
||||
registerServiceBroadcastReceiver();
|
||||
|
||||
try {
|
||||
Parcel parcel = createRequest("GetInstances");
|
||||
Parcel retParcel = Parcel.obtain();
|
||||
|
||||
m_service.transact(1, parcel, retParcel, 0);
|
||||
|
||||
JSONObject reply = new JSONObject(retParcel.readString());
|
||||
Log.d(TAG, "Instaces received: " + reply.toString());
|
||||
JSONArray instances = reply.getJSONArray("instances");
|
||||
for (int i = 0; i < instances.length(); i++) {
|
||||
JSONObject instanceMap = instances.getJSONObject(i);
|
||||
NymeaHost nymeaHost = new NymeaHost();
|
||||
nymeaHost.id = UUID.fromString(instanceMap.getString("id"));
|
||||
nymeaHost.name = instanceMap.getString("name");
|
||||
nymeaHost.isReady = instanceMap.getBoolean("isReady");
|
||||
m_nymeaHosts.put(nymeaHost.id, nymeaHost);
|
||||
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.d(TAG, "Error while processing JSON in communication with NymeaAppService: " + e.toString());
|
||||
onError();
|
||||
return;
|
||||
} catch (RemoteException e) {
|
||||
Log.d(TAG, "Error communicating with NymeaAppService: " + e.toString());
|
||||
onError();
|
||||
return;
|
||||
}
|
||||
|
||||
m_connected = true;
|
||||
onConnectedChanged(m_connected);
|
||||
}
|
||||
|
||||
@Override public void onServiceDisconnected(ComponentName arg0) {
|
||||
m_service = null;
|
||||
for (int i = 0; i < m_nymeaHosts.size(); i++) {
|
||||
m_nymeaHosts.get(i).isReady = false;
|
||||
}
|
||||
m_connected = false;
|
||||
onConnectedChanged(m_connected);
|
||||
}
|
||||
|
||||
public void registerServiceBroadcastReceiver() {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(NymeaAppService.NYMEA_APP_BROADCAST);
|
||||
m_context.registerReceiver(serviceMessageReceiver, intentFilter);
|
||||
Log.d(TAG, "Registered broadcast receiver");
|
||||
}
|
||||
|
||||
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 {
|
||||
processBroadcast(payload);
|
||||
} catch(JSONException e) {
|
||||
Log.d(TAG, "Error parsing broadcast JSON: " + e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void processBroadcast(String payload) throws JSONException
|
||||
{
|
||||
JSONObject data = new JSONObject(payload);
|
||||
JSONObject params = data.getJSONObject("params");
|
||||
Log.d(TAG, "Broadcast received from NymeaAppService: " + data.getString("notification"));
|
||||
Log.d(TAG, params.toString());
|
||||
|
||||
if (data.getString("notification").equals("ThingStateChanged")) {
|
||||
UUID nymeaId = UUID.fromString(params.getString("nymeaId"));
|
||||
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);
|
||||
|
||||
Thing thing = getThing(thingId);
|
||||
if (thing != null) {
|
||||
thing.stateById(stateTypeId).value = value;
|
||||
onUpdate(nymeaId, thingId);
|
||||
} else {
|
||||
Log.d(TAG, "Got a state change notification for a thing we don't know!");
|
||||
}
|
||||
}
|
||||
|
||||
if (data.getString("notification").equals("ReadyStateChanged")) {
|
||||
UUID nymeaId = UUID.fromString(params.getString("nymeaId"));
|
||||
NymeaHost host = m_nymeaHosts.get(nymeaId);
|
||||
host.isReady = params.getBoolean("isReady");
|
||||
if (host.isReady) {
|
||||
Log.d(TAG, "Host is ready. Fetching things...");
|
||||
fetchThings(nymeaId);
|
||||
} else {
|
||||
Log.d(TAG, "Host is not ready yet...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchThings(UUID nymeaId) {
|
||||
Log.d(TAG, "Fetching things");
|
||||
String thingsList;
|
||||
try {
|
||||
JSONObject params = new JSONObject();
|
||||
params.put("nymeaId", nymeaId.toString());
|
||||
Parcel parcel = createRequest("GetThings", params);
|
||||
Parcel retParcel = Parcel.obtain();
|
||||
m_service.transact(1, parcel, retParcel, 0);
|
||||
thingsList = retParcel.readString();
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Error fetching things from NymeaAppService");
|
||||
onError();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject result = new JSONObject(thingsList);
|
||||
for (int i = 0; i < result.getJSONArray("things").length(); i++) {
|
||||
JSONObject entry = result.getJSONArray("things").getJSONObject(i);
|
||||
Thing thing = new Thing();
|
||||
thing.id = UUID.fromString(entry.getString("id"));
|
||||
thing.name = entry.getString("name");
|
||||
thing.className = entry.getString("className");
|
||||
JSONArray ifaces = entry.getJSONArray("interfaces");
|
||||
for (int j = 0; j < ifaces.length(); j++) {
|
||||
thing.interfaces.add(ifaces.get(j));
|
||||
}
|
||||
JSONArray states = entry.getJSONArray("states");
|
||||
for (int j = 0; j < states.length(); j++) {
|
||||
JSONObject stateMap = states.getJSONObject(j);
|
||||
State s = new State();
|
||||
s.typeId = UUID.fromString(stateMap.getString("stateTypeId"));
|
||||
s.name = stateMap.getString("name");
|
||||
s.displayName = stateMap.getString("displayName");
|
||||
s.value = stateMap.getString("value");
|
||||
thing.states.add(s);
|
||||
}
|
||||
JSONArray actions = entry.getJSONArray("actions");
|
||||
for (int j = 0; j < actions.length(); j++) {
|
||||
JSONObject actionMap = actions.getJSONObject(j);
|
||||
Action a = new Action();
|
||||
a.typeId = UUID.fromString(actionMap.getString("actionTypeId"));
|
||||
a.name = actionMap.getString("name");
|
||||
a.displayName = actionMap.getString("displayName");
|
||||
thing.actions.add(a);
|
||||
}
|
||||
m_nymeaHosts.get(nymeaId).things.put(thing.id, thing);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Error parsing JSON from NymeaAppService: " + e.toString());
|
||||
Log.d(TAG, thingsList);
|
||||
m_service = null;
|
||||
onError();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Things fetched: " + m_nymeaHosts.get(nymeaId).things.size());
|
||||
m_nymeaHosts.get(nymeaId).isReady = true;
|
||||
onReadyChanged(nymeaId, true);
|
||||
}
|
||||
|
||||
private Parcel createRequest(String method) throws JSONException {
|
||||
return createRequest(method, null);
|
||||
}
|
||||
private Parcel createRequest(String method, JSONObject params) throws JSONException {
|
||||
Parcel ret = Parcel.obtain();
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("method", method);
|
||||
if (params != null) {
|
||||
payload.put("params", params);
|
||||
}
|
||||
Log.d(TAG, "Parcel payload: " + payload.toString());
|
||||
ret.writeString(payload.toString());
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
13
packaging/android/src/io/guh/nymeaapp/NymeaHost.java
Normal file
13
packaging/android/src/io/guh/nymeaapp/NymeaHost.java
Normal file
@ -0,0 +1,13 @@
|
||||
package io.guh.nymeaapp;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
public class NymeaHost {
|
||||
|
||||
UUID id;
|
||||
boolean isReady = false;
|
||||
String name = "";
|
||||
HashMap<UUID, Thing> things = new HashMap<UUID, Thing>();
|
||||
}
|
||||
10
packaging/android/src/io/guh/nymeaapp/State.java
Normal file
10
packaging/android/src/io/guh/nymeaapp/State.java
Normal file
@ -0,0 +1,10 @@
|
||||
package io.guh.nymeaapp;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class State {
|
||||
public UUID typeId;
|
||||
public String name;
|
||||
public String displayName;
|
||||
public String value;
|
||||
}
|
||||
56
packaging/android/src/io/guh/nymeaapp/Thing.java
Normal file
56
packaging/android/src/io/guh/nymeaapp/Thing.java
Normal file
@ -0,0 +1,56 @@
|
||||
package io.guh.nymeaapp;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
public class Thing {
|
||||
static final public String TAG = "nymea-app: Thing";
|
||||
public UUID id;
|
||||
public String name;
|
||||
public String className;
|
||||
public List interfaces = new ArrayList<State>();
|
||||
|
||||
public ArrayList<State> states = new ArrayList<State>();
|
||||
public ArrayList<Action> actions = new ArrayList<Action>();
|
||||
|
||||
public State stateByName(String name) {
|
||||
for (int i = 0; i < states.size(); i++) {
|
||||
if (states.get(i).name.equals(name)) {
|
||||
return states.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public State stateById(UUID stateTypeId) {
|
||||
for (int i = 0; i < states.size(); i++) {
|
||||
if (states.get(i).typeId.equals(stateTypeId)) {
|
||||
return states.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Action actionByName(String name) {
|
||||
for (int i = 0; i < actions.size(); i++) {
|
||||
Log.d(TAG, "Thing has action: " + actions.get(i).name);
|
||||
if (actions.get(i).name.equals(name)) {
|
||||
return actions.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Action actionById(UUID actionTypeId) {
|
||||
for (int i = 0; i < actions.size(); i++) {
|
||||
if (actions.get(i).typeId.equals(actionTypeId)) {
|
||||
return actions.get(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user