Support multiple nymea hosts

pull/432/head
Michael Zanetti 2020-09-20 17:59:06 +02:00
parent e9c345cc8b
commit c8a91ba9b1
10 changed files with 410 additions and 193 deletions

View File

@ -26,27 +26,29 @@ DeviceControlApplication::DeviceControlApplication(int argc, char *argv[]) : QAp
setApplicationName("nymea-app");
setOrganizationName("nymea");
qDebug() << "Creating QML view";
QQmlApplicationEngine *qmlEngine = new QQmlApplicationEngine(this);
Engine *m_engine = new Engine(this);
QString nymeaId = QtAndroid::androidActivity().callObjectMethod<jstring>("nymeaId").toString();
QString thingId = QtAndroid::androidActivity().callObjectMethod<jstring>("thingId").toString();
QSettings settings;
settings.beginGroup("tabSettings0");
QUuid lastConnected = settings.value("lastConnectedHost").toUuid();
settings.endGroup();
NymeaDiscovery *discovery = new NymeaDiscovery(this);
AWSClient::instance()->setConfig(settings.value("cloudEnvironment").toString());
discovery->setAwsClient(AWSClient::instance());
NymeaHost *host = discovery->nymeaHosts()->find(nymeaId);
NymeaHost *host = discovery->nymeaHosts()->find(lastConnected);
qDebug() << "**** Tab settings" << lastConnected << host;
if (host) {
m_engine->jsonRpcClient()->connectToHost(host);
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);
}
QString thingId = QtAndroid::androidActivity().callObjectMethod<jstring>("thingId").toString();
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();

View File

@ -8,32 +8,53 @@
#include <QJsonDocument>
#include <QtAndroid>
AndroidBinder::AndroidBinder(Engine * engine):
m_engine(engine)
AndroidBinder::AndroidBinder(NymeaAppService *service):
m_service(service)
{
QAndroidParcel parcel;
parcel.writeData("foobar");
transact(10, parcel);
}
bool AndroidBinder::onTransact(int code, const QAndroidParcel &data, const QAndroidParcel &reply, QAndroidBinder::CallType flags)
{
qDebug() << "onTransact: code " << code << ", flags " << int(flags);
switch (code) {
case 0: { // Status request
qDebug() << "Engine is:" << m_engine->jsonRpcClient()->connected();
bool isReady = m_engine->jsonRpcClient()->connected() && !m_engine->thingManager()->fetchingData();
isReady = false;
reply.handle().callMethod<void>("writeBoolean", "(Z)V", isReady);
if (isReady) {
reply.handle().callMethod<void>("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(m_engine->jsonRpcClient()->currentHost()->name()).object<jstring>());
// 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;
}
} break;
case 1: {// Things request
QVariantList thingsList;
for (int i = 0; i < m_engine->thingManager()->things()->rowCount(); i++) {
Device *thing = m_engine->thingManager()->things()->get(i);
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());
@ -62,41 +83,110 @@ bool AndroidBinder::onTransact(int code, const QAndroidParcel &data, const QAndr
thingMap.insert("actions", actions);
thingsList.append(thingMap);
}
QJsonDocument jsonDoc = QJsonDocument::fromVariant(thingsList);
reply.handle().callMethod<void>("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(jsonDoc.toJson()).object<jstring>());
} break;
case 2: {// ExecuteAction
// QString thingId = data.handle().callMethod<QAndroidJniObject>("readString", "").toString();
// jstring atId = data.handle().callMethod<jstring>("readString", "");
// QString actionTypeId = QAndroidJniObject::fromLocalRef(atId).toString();
// jstring p = data.handle().callMethod<jstring>("readString", "");
// QString param = QAndroidJniObject::fromLocalRef(p).toString();
qDebug() << "ExecuteAction";
QString thingId = data.readData();
QString actionTypeId = data.readData();
QString param = data.readData();
qDebug() << "**** executeAction:" << thingId << actionTypeId << param;
// FIXME: Only works with state generated actions!
QVariantMap paramMap;
paramMap.insert("paramTypeId", actionTypeId);
paramMap.insert("value", param);
m_engine->thingManager()->executeAction(thingId, actionTypeId, {paramMap});
} break;
// default:
// QAndroidBinder binder = data.readBinder();
// qDebug() << TAG << ": onTransact() received non-name data" << data.readVariant();
// reply.writeVariant(QVariant("Cannot process this!"));
// // send back message
// QAndroidParcel sendData, replyData;
// sendData.writeVariant(QVariant("Send me only names!"));
// binder.transact(0, sendData, &replyData);
// qDebug() << TAG << ": onTransact() received " << replyData.readData();
// break;
QVariantMap params;
params.insert("things", thingsList);
sendReply(reply, params);
return true;
}
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);
}
// switch (code) {
// case 0: { // Status request
// bool isReady = m_engine->jsonRpcClient()->connected() && !m_engine->thingManager()->fetchingData();
// isReady = false;
// reply.handle().callMethod<void>("writeBoolean", "(Z)V", isReady);
// if (isReady) {
// reply.handle().callMethod<void>("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(m_engine->jsonRpcClient()->currentHost()->name()).object<jstring>());
// }
// } break;
// case 1: {// Things request
// QVariantList thingsList;
// for (int i = 0; i < m_engine->thingManager()->things()->rowCount(); i++) {
// Device *thing = m_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);
// }
// QJsonDocument jsonDoc = QJsonDocument::fromVariant(thingsList);
// reply.handle().callMethod<void>("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(jsonDoc.toJson()).object<jstring>());
// } break;
// case 2: {// ExecuteAction
//// QString thingId = data.handle().callMethod<QAndroidJniObject>("readString", "").toString();
//// jstring atId = data.handle().callMethod<jstring>("readString", "");
//// QString actionTypeId = QAndroidJniObject::fromLocalRef(atId).toString();
//// jstring p = data.handle().callMethod<jstring>("readString", "");
//// QString param = QAndroidJniObject::fromLocalRef(p).toString();
// qDebug() << "ExecuteAction";
// QString thingId = data.readData();
// QString actionTypeId = data.readData();
// QString param = data.readData();
// qDebug() << "**** executeAction:" << thingId << actionTypeId << param;
// // FIXME: Only works with state generated actions!
// QVariantMap paramMap;
// paramMap.insert("paramTypeId", actionTypeId);
// paramMap.insert("value", param);
// m_engine->thingManager()->executeAction(thingId, actionTypeId, {paramMap});
// } break;
//// default:
//// QAndroidBinder binder = data.readBinder();
//// qDebug() << TAG << ": onTransact() received non-name data" << data.readVariant();
//// reply.writeVariant(QVariant("Cannot process this!"));
//// // send back message
//// QAndroidParcel sendData, replyData;
//// sendData.writeVariant(QVariant("Send me only names!"));
//// binder.transact(0, sendData, &replyData);
//// qDebug() << TAG << ": onTransact() received " << replyData.readData();
//// break;
// }
return false;
}
void AndroidBinder::sendReply(const QAndroidParcel &reply, const QVariantMap &params)
{
QString payload = QJsonDocument::fromVariant(params).toJson();
reply.handle().callMethod<void>("writeString", "(Ljava/lang/String;)V", QAndroidJniObject::fromString(payload).object<jstring>());
}

View File

@ -3,17 +3,21 @@
#include <QAndroidBinder>
#include "nymeaappservice.h"
#include "engine.h"
class AndroidBinder : public QAndroidBinder
{
public:
explicit AndroidBinder(Engine *engine);
explicit AndroidBinder(NymeaAppService *service);
bool onTransact(int code, const QAndroidParcel &data, const QAndroidParcel &reply, QAndroidBinder::CallType flags) override;
private:
Engine *m_engine = nullptr;
void sendReply(const QAndroidParcel &reply, const QVariantMap &params);
private:
NymeaAppService *m_service = nullptr;
};
#endif // ANDROIDBINDER_H

View File

@ -11,44 +11,63 @@
NymeaAppService::NymeaAppService(int argc, char **argv):
QAndroidService(argc, argv, [=](const QAndroidIntent &) {
return new AndroidBinder{m_engine};
return new AndroidBinder{this};
})
{
setApplicationName("nymea-app");
setOrganizationName("nymea");
m_engine = new Engine(this);
QSettings settings;
settings.beginGroup("tabSettings0");
QUuid lastConnected = settings.value("lastConnectedHost").toUuid();
settings.endGroup();
NymeaDiscovery *discovery = new NymeaDiscovery(this);
AWSClient::instance()->setConfig(settings.value("cloudEnvironment").toString());
discovery->setAwsClient(AWSClient::instance());
NymeaHost *host = discovery->nymeaHosts()->find(lastConnected);
if (host) {
m_engine->jsonRpcClient()->connectToHost(host);
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);
});
}
QObject::connect(m_engine->thingManager(), &DeviceManager::thingStateChanged, [=](const QUuid &thingId, const QUuid &stateTypeId, const QVariant &value){
QVariantMap params;
params.insert("thingId", thingId);
params.insert("stateTypeId", stateTypeId);
params.insert("value", value);
sendNotification("ThingStateChanged", params);
});
qDebug() << "NymeaAppService started.";
connect(m_engine->thingManager(), &DeviceManager::fetchingDataChanged, [=]() {
QVariantMap params;
params.insert("isReady", !m_engine->thingManager()->fetchingData());
if (m_engine->jsonRpcClient()->connected()) {
params.insert("systemName", m_engine->jsonRpcClient()->currentHost()->name());
}
sendNotification("ReadyStateChanged", params);
});
}
QHash<QUuid, Engine *> NymeaAppService::engines() const
{
return m_engines;
}
void NymeaAppService::sendNotification(const QString &notification, const QVariantMap &params)

View File

@ -11,12 +11,14 @@ class NymeaAppService : public QAndroidService
public:
explicit NymeaAppService(int argc, char** argv);
QHash<QUuid, Engine*> engines() const;
private:
void sendNotification(const QString &notification, const QVariantMap &params);
private:
Engine *m_engine = nullptr;
QHash<QUuid, Engine*> m_engines;
};

View File

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

View File

@ -36,7 +36,10 @@ 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;
@ -44,52 +47,84 @@ public class NymeaAppControlService extends ControlsProviderService {
private void ensureServiceConnection() {
if (m_serviceConnection == null) {
m_serviceConnection = new NymeaAppServiceConnection(getBaseContext()) {
@Override public void onReady() {
process();
@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 onUpdate(UUID thingId) {
@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())) {
Thing thing = m_serviceConnection.getThing(thingId);
Log.d(TAG, "Updating publisher for thing: " + thing.name + " id: " + thing.id);
m_updatePublisher.onNext(thingToControl(thing));
Log.d(TAG, "Updating publisher for thing: " + thingId);
m_updatePublisher.onNext(thingToControl(nymeaId, thingId));
// m_updatePublisher.onComplete();
}
}
};
}
if (!m_serviceConnection.isConnected()) {
Intent serviceIntent = new Intent(this, NymeaAppService.class);
bindService(serviceIntent, m_serviceConnection, Context.BIND_AUTO_CREATE);
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() {
private void process(UUID nymeaId) {
Log.d(TAG, "Processing...");
ensureServiceConnection();
if (!m_serviceConnection.isReady()) {
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.getThings()) {
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(thing));
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(thing));
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) {
if (m_publisherForAll != null && m_pendingForAll.isEmpty()) {
Log.d(TAG, "Completing all publisher");
m_publisherForAll.onComplete();
}
@ -103,7 +138,7 @@ public class NymeaAppControlService extends ControlsProviderService {
public Publisher createPublisherForAllAvailable() {
Log.d(TAG, "Creating publishers for all");
m_publisherForAll = ReplayProcessor.create();
process();
processAll();
return FlowAdapters.toFlowPublisher(m_publisherForAll);
}
@ -112,19 +147,20 @@ public class NymeaAppControlService extends ControlsProviderService {
Log.d(TAG, "Creating publishers for " + Integer.toString(controlIds.size()));
m_updatePublisher = ReplayProcessor.create();
m_activeControlIds = controlIds;
process();
processAll();
return FlowAdapters.toFlowPublisher(m_updatePublisher);
}
@Override
public void performControlAction(String controlId, ControlAction action, Consumer consumer) {
Log.d(TAG, "Performing control action: " + controlId);
//// PendingAction pendingAction = new PendingAction();
//// pendingAction.thingId = controlId;
//// pendingAction.actionTypeId = "";
//// pendingAction.consumer = consumer;
//// m_pendingActions.put(
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);
@ -162,17 +198,21 @@ public class NymeaAppControlService extends ControlsProviderService {
return;
}
m_serviceConnection.executeAction(thing.id, actionTypeId, param);
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(Thing thing) {
private Control thingToControl(UUID nymeaId, UUID thingId) {
// Log.d(TAG, "Creating control for thing: " + thing.name + " id: " + thing.id);
// 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
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)) {
@ -184,6 +224,7 @@ public class NymeaAppControlService extends ControlsProviderService {
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);
@ -191,7 +232,7 @@ public class NymeaAppControlService extends ControlsProviderService {
Control.StatefulBuilder builder = new Control.StatefulBuilder(thing.id.toString(), pi)
.setTitle(thing.name)
.setSubtitle(thing.className)
.setStructure(m_serviceConnection.nymeaName());
.setStructure(nymeaHost.name);
if (thing.interfaces.contains("impulsebasedgaragedoor")) {
builder.setDeviceType(DeviceTypes.TYPE_GARAGE);

View File

@ -32,10 +32,13 @@ public class NymeaAppControlsActivity extends org.qtproject.qt5.android.bindings
}
public String nymeaId()
{
return getIntent().getStringExtra("nymeaId");
}
public String thingId()
{
Log.d(TAG, "ThingId called!");
Log.d(TAG, getIntent().getStringExtra("thingId"));
return getIntent().getStringExtra("thingId");
}

View File

@ -3,11 +3,13 @@ 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;
@ -29,59 +31,63 @@ import org.json.*;
public class NymeaAppServiceConnection implements ServiceConnection {
private static final String TAG = "nymea-app: NymeaAppServiceConnection";
private IBinder m_service;
private boolean m_isConnectedToNymea = false;
private boolean m_isReady = false;
private Context m_context;
private String m_nymeaName = "nymea";
private ArrayList<Thing> m_things = new ArrayList<>();
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 isConnected() {
return m_service != null;
final public boolean connected() {
return m_connected;
}
public void onConnectedChanged(boolean connected) {};
public final HashMap<UUID, NymeaHost> getHosts() {
return m_nymeaHosts;
}
final public boolean isConnectedToNymea() {
return m_isConnectedToNymea;
}
final public String nymeaName() {
return m_nymeaName;
}
final public boolean isReady() {
return m_isReady;
}
final public ArrayList<Thing> getThings() {
return m_things;
}
final public Thing getThing(UUID thingId) {
for (int i = 0; i < m_things.size(); i++) {
if (m_things.get(i).id.equals(thingId)) {
return m_things.get(i);
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 onReady() {}
public void onReadyChanged(UUID nymeaId, boolean ready) {}
public void onError() {}
public void onUpdate(UUID thingId) {}
public void onUpdate(UUID nymeaId, UUID thingId) {}
final public void executeAction(UUID thingId, UUID actionTypeId, String param) {
final public void executeAction(UUID nymeaId, UUID thingId, UUID actionTypeId, String paramValue) {
try {
Parcel parcel = Parcel.obtain();
parcel.writeByteArray(thingId.toString().getBytes());
parcel.writeByteArray(actionTypeId.toString().getBytes());
parcel.writeByteArray(param.getBytes());
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(2, parcel, retParcel, 0);
// thingsList = retParcel.readString();
m_service.transact(1, parcel, retParcel, 0);
} catch (Exception e) {
Log.d(TAG, "Error calling executeAction on NymeaAppService");
}
@ -94,30 +100,44 @@ public class NymeaAppServiceConnection implements ServiceConnection {
registerServiceBroadcastReceiver();
try {
Parcel parcel = Parcel.obtain();
Parcel parcel = createRequest("GetInstances");
Parcel retParcel = Parcel.obtain();
m_service.transact(0, parcel, retParcel, 0);
m_isReady = retParcel.readBoolean();
if (!m_isReady) {
m_nymeaName = retParcel.readString();
m_isConnectedToNymea = true;
Log.d(TAG, "Service is ready!");
fetchThings();
} else {
Log.d(TAG, "Service is not ready yet!");
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 (Exception e) {
Log.d(TAG, "Error while waiting for service to be connected to nymea");
m_service = null;
} 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;
m_isConnectedToNymea = false;
m_isReady = false;
for (int i = 0; i < m_nymeaHosts.size(); i++) {
m_nymeaHosts.get(i).isReady = false;
}
m_connected = false;
onConnectedChanged(m_connected);
}
public void registerServiceBroadcastReceiver() {
@ -130,7 +150,7 @@ 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");
Log.d(TAG, "In OnReceive broadcast receiver");
if (NymeaAppService.NYMEA_APP_BROADCAST.equals(intent.getAction())) {
String payload = intent.getStringExtra("data");
try {
@ -147,52 +167,57 @@ 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, 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);
for (int i = 0; i < m_things.size(); i++) {
if (m_things.get(i).id.equals(thingId)) {
m_things.get(i).stateById(stateTypeId).value = value;
onUpdate(thingId);
}
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")) {
m_isReady = params.getBoolean("isReady");
if (m_isReady) {
m_nymeaName = params.getString("systemName");
fetchThings();
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() {
private void fetchThings(UUID nymeaId) {
Log.d(TAG, "Fetching things");
String thingsList;
try {
Log.d(TAG, "Fetching things");
Parcel parcel = Parcel.obtain();
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();
Log.d(TAG, "Things fetched");
} catch (Exception e) {
Log.d(TAG, "Error fetching things from NymeaAppService");
m_service = null;
m_isConnectedToNymea = false;
onError();
return;
}
try {
Log.d(TAG, "Parsing JSON");
JSONArray arr = new JSONArray(thingsList);
for (int i = 0; i < arr.length(); i++) {
JSONObject entry = arr.getJSONObject(i);
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");
@ -220,19 +245,34 @@ public class NymeaAppServiceConnection implements ServiceConnection {
a.displayName = actionMap.getString("displayName");
thing.actions.add(a);
}
m_things.add(thing);
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;
m_isConnectedToNymea = false;
onError();
return;
}
Log.d(TAG, "Fetched things");
m_isReady = true;
onReady();
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;
}
}

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