getting close

This commit is contained in:
Michael Zanetti 2018-08-17 23:57:01 +02:00
parent 080a47932a
commit e9390dcb5a
23 changed files with 802 additions and 48 deletions

View File

@ -6,6 +6,7 @@
#include <QUrlQuery>
#include <QJsonDocument>
#include <QSettings>
#include <QUuid>
#include "qmqtt.h"
#include "sigv4utils.h"
@ -15,7 +16,38 @@ static QByteArray region = "eu-west-1";
//static QByteArray service = "iotdevicegateway";
static QByteArray service = "iotdata";
AWSClient::AWSClient(QObject *parent) : QObject(parent)
static QByteArray rootCA = "-----BEGIN CERTIFICATE-----\n\
MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB\n\
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL\n\
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp\n\
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW\n\
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0\n\
aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL\n\
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW\n\
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln\n\
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp\n\
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y\n\
aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1\n\
nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex\n\
t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz\n\
SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG\n\
BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+\n\
rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/\n\
NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E\n\
BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH\n\
BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy\n\
aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv\n\
MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE\n\
p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y\n\
5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK\n\
WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ\n\
4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N\n\
hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq\n\
-----END CERTIFICATE-----\n\
";
AWSClient::AWSClient(QObject *parent) : QObject(parent),
m_devices(new AWSDevices(this))
{
m_nam = new QNetworkAccessManager(this);
@ -46,6 +78,16 @@ QString AWSClient::username() const
return m_username;
}
QByteArray AWSClient::userId() const
{
return m_identityId;
}
AWSDevices *AWSClient::awsDevices() const
{
return m_devices;
}
void AWSClient::login(const QString &username, const QString &password)
{
m_username = username;
@ -189,6 +231,46 @@ QByteArray AWSClient::idToken() const
return m_idToken;
}
QString AWSClient::cognitoIdentityId() const
{
return m_identityId;
}
void AWSClient::fetchCertificate(const QString &uuid, std::function<void(const QByteArray &, const QByteArray &, const QByteArray &, const QByteArray &, const QString &)> callback)
{
QString fixedUuid = uuid;
fixedUuid.remove(QRegExp("[{}]"));
QNetworkRequest request(QUrl("https://testproductionservice-cloud.guh.io/certificatews/certificate"));
request.setRawHeader("X-api-key", "BJMq4h19dB5yjVKwagTvk9u72FqLecEoWPJIkyDj");
request.setRawHeader("X-api-vendorId", "testVendor001");
request.setRawHeader("X-api-deviceId", fixedUuid.toUtf8());
request.setRawHeader("X-api-serialId", "69696969");
QNetworkReply *reply = m_nam->get(request);
connect(reply, &QNetworkReply::finished, this, [reply, callback]() {
reply->deleteLater();
QByteArray data = reply->readAll();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error deploying certificate" << data;
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "Error parsing certificate json" << data;
return;
}
QByteArray certificate = jsonDoc.toVariant().toMap().value("certificatePem").toByteArray();
QByteArray publicKey = jsonDoc.toVariant().toMap().value("keyPair").toMap().value("PublicKey").toByteArray();
QByteArray privateKey = jsonDoc.toVariant().toMap().value("keyPair").toMap().value("PrivateKey").toByteArray();
qDebug() << "Certificate received" << certificate;
qDebug() << "Public key" << publicKey;
qDebug() << "Private key" << privateKey;
callback(rootCA, certificate, publicKey, privateKey, "a2addxakg5juii.iot.eu-west-1.amazonaws.com");
});
}
void AWSClient::getCredentialsForIdentity(const QString &identityId)
{
QUrl url("https://cognito-identity.eu-west-1.amazonaws.com/");
@ -367,15 +449,21 @@ void AWSClient::fetchDevices()
qWarning() << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data);
return;
}
QList<AWSDevice> ret;
foreach (const QVariant &entry, jsonDoc.toVariant().toMap().value("devices").toList()) {
AWSDevice d;
d.id = entry.toMap().value("deviceId").toString();
d.name = entry.toMap().value("name").toString();
d.online = entry.toMap().value("online").toBool();
ret.append(d);
QString deviceId = entry.toMap().value("deviceId").toString();
QString name = entry.toMap().value("name").toString();
bool online = entry.toMap().value("online").toBool();
qDebug() << "Have cloud device:" << deviceId << name << "online:" << online;
AWSDevice *d = m_devices->getDevice(deviceId);
if (!d) {
d = new AWSDevice(deviceId, name);
m_devices->insert(d);
}
d->setOnline(online);
}
emit devicesFetched(ret);
emit devicesFetched();
});
}
@ -455,3 +543,96 @@ void AWSClient::refreshAccessToken()
});
}
AWSDevices::AWSDevices(QObject *parent):
QAbstractListModel(parent)
{
}
int AWSDevices::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_list.count();
}
QVariant AWSDevices::data(const QModelIndex &index, int role) const
{
switch (role) {
case RoleName:
return m_list.at(index.row())->name();
case RoleId:
return m_list.at(index.row())->id();
case RoleOnline:
return m_list.at(index.row())->online();
}
return QVariant();
}
QHash<int, QByteArray> AWSDevices::roleNames() const
{
QHash<int, QByteArray> roles;
roles.insert(RoleName, "name");
roles.insert(RoleId, "id");
roles.insert(RoleOnline, "online");
return roles;
}
AWSDevice *AWSDevices::getDevice(const QString &uuid) const
{
for (int i = 0; i < m_list.count(); i++) {
if (m_list.at(i)->id() == uuid) {
return m_list.at(i);
}
}
return nullptr;
}
AWSDevice *AWSDevices::get(int index) const
{
if (index < 0 || index >= m_list.count()) {
return nullptr;
}
return m_list.at(index);
}
void AWSDevices::insert(AWSDevice *device)
{
device->setParent(this);
beginInsertRows(QModelIndex(), m_list.count(), m_list.count());
m_list.append(device);
endInsertRows();
emit countChanged();
}
AWSDevice::AWSDevice(const QString &id, const QString &name, bool online, QObject *parent):
QObject (parent),
m_id(id),
m_name(name),
m_online(online)
{
}
QString AWSDevice::id() const
{
return m_id;
}
QString AWSDevice::name() const
{
return m_name;
}
bool AWSDevice::online() const
{
return m_online;
}
void AWSDevice::setOnline(bool online)
{
if (m_online != online) {
m_online = online;
emit onlineChanged();
}
}

View File

@ -4,14 +4,52 @@
#include <QObject>
#include <QNetworkRequest>
#include <QDate>
#include <QAbstractListModel>
class QNetworkAccessManager;
class AWSDevice {
class AWSDevice: public QObject {
Q_OBJECT
Q_PROPERTY(QString id READ id CONSTANT)
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(bool online READ online NOTIFY onlineChanged)
public:
QString id;
QString name;
bool online;
AWSDevice(const QString &id, const QString &name, bool online = false, QObject *parent = nullptr);
QString id() const;
QString name() const;
bool online() const;
void setOnline(bool online);
signals:
void onlineChanged();
private:
QString m_id;
QString m_name;
bool m_online;
};
class AWSDevices: public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum Roles {
RoleName,
RoleId,
RoleOnline
};
AWSDevices(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE AWSDevice* getDevice(const QString &uuid) const;
Q_INVOKABLE AWSDevice* get(int index) const;
void insert(AWSDevice *device);
signals:
void countChanged();
private:
QList<AWSDevice*> m_list;
};
class AWSClient : public QObject
@ -19,12 +57,17 @@ class AWSClient : public QObject
Q_OBJECT
Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY isLoggedInChanged)
Q_PROPERTY(QString username READ username NOTIFY isLoggedInChanged)
Q_PROPERTY(QByteArray userId READ userId NOTIFY isLoggedInChanged)
Q_PROPERTY(QByteArray idToken READ idToken NOTIFY isLoggedInChanged)
Q_PROPERTY(AWSDevices* awsDevices READ awsDevices CONSTANT)
public:
explicit AWSClient(QObject *parent = nullptr);
bool isLoggedIn() const;
QString username() const;
QByteArray userId() const;
AWSDevices* awsDevices() const;
Q_INVOKABLE void login(const QString &username, const QString &password);
Q_INVOKABLE void logout();
@ -36,11 +79,13 @@ public:
bool tokensExpired() const;
QByteArray idToken() const;
QString cognitoIdentityId() const;
void fetchCertificate(const QString &uuid, std::function<void(const QByteArray &rootCA, const QByteArray &certificate, const QByteArray &publicKey, const QByteArray &privateKey, const QString &endpoint)> callback);
signals:
void isLoggedInChanged();
void devicesFetched(QList<AWSDevice> devices);
void devicesFetched();
private:
void refreshAccessToken();
@ -76,6 +121,8 @@ private:
};
QList<QueuedCall> m_callQueue;
AWSDevices *m_devices;
};
#endif // AWSCLIENT_H

View File

@ -13,7 +13,6 @@ CloudTransport::CloudTransport(AWSClient *awsClient, QObject *parent):
m_awsClient(awsClient)
{
m_remoteproxyConnection = new RemoteProxyConnection(QUuid::createUuid(), "nymea:app", this);
m_remoteproxyConnection->setInsecureConnection(true);
QObject::connect(m_remoteproxyConnection, &RemoteProxyConnection::remoteConnectionEstablished, this,[this]() {
qDebug() << "CloudTransport: Remote connection established.";
@ -37,8 +36,9 @@ CloudTransport::CloudTransport(AWSClient *awsClient, QObject *parent):
});
QObject::connect(m_remoteproxyConnection, &RemoteProxyConnection::errorOccured, this, [this] (RemoteProxyConnection::Error error) {
qDebug() << "Remote proxy Error:" << error;
emit NymeaTransportInterface::error(QAbstractSocket::ConnectionRefusedError);
// emit NymeaTransportInterface::error(QAbstractSocket::ConnectionRefusedError);
});
QObject::connect(m_remoteproxyConnection, &RemoteProxyConnection::sslErrors, this, &CloudTransport::sslErrors);
}
QStringList CloudTransport::supportedSchemes() const
@ -57,7 +57,8 @@ bool CloudTransport::connect(const QUrl &url)
bool postResult = m_awsClient->postToMQTT(url.host(), [this](bool success) {
if (success) {
m_remoteproxyConnection->connectServer(QHostAddress("34.244.242.103"), 443);
m_remoteproxyConnection->connectServer(QUrl("wss://dev-remoteproxy.nymea.io"));
// m_remoteproxyConnection->connectServer(QUrl("wss://127.0.0.1:1212"));
}
});
@ -81,6 +82,7 @@ NymeaTransportInterface::ConnectionState CloudTransport::connectionState() const
case RemoteProxyConnection::StateRemoteConnected:
return NymeaTransportInterface::ConnectionStateConnected;
case RemoteProxyConnection::StateInitializing:
case RemoteProxyConnection::StateHostLookup:
case RemoteProxyConnection::StateConnecting:
case RemoteProxyConnection::StateConnected:
case RemoteProxyConnection::StateAuthenticating:
@ -98,3 +100,9 @@ void CloudTransport::sendData(const QByteArray &data)
qDebug() << "should send" << data;
m_remoteproxyConnection->sendData(data);
}
void CloudTransport::ignoreSslErrors(const QList<QSslError> &errors)
{
qDebug() << "Ignoring SSL errors" << errors;
m_remoteproxyConnection->ignoreSslErrors(errors);
}

View File

@ -23,6 +23,7 @@ public:
ConnectionState connectionState() const override;
void sendData(const QByteArray &data) override;
void ignoreSslErrors(const QList<QSslError> &errors) override;
private:
AWSClient *m_awsClient = nullptr;
remoteproxyclient::RemoteProxyConnection *m_remoteproxyConnection = nullptr;

View File

@ -162,3 +162,16 @@ QString Connection::displayName() const
{
return m_displayName;
}
bool Connection::online() const
{
return m_online;
}
void Connection::setOnline(bool online)
{
if (m_online != online) {
m_online = online;
emit onlineChanged();
}
}

View File

@ -35,6 +35,7 @@ class Connection: public QObject {
Q_PROPERTY(BearerType bearerType READ bearerType CONSTANT)
Q_PROPERTY(bool secure READ secure CONSTANT)
Q_PROPERTY(QString displayName READ displayName CONSTANT)
Q_PROPERTY(bool online READ online NOTIFY onlineChanged)
public:
enum BearerType {
BearerTypeUnknown,
@ -51,12 +52,18 @@ public:
BearerType bearerType() const;
bool secure() const;
QString displayName() const;
bool online() const;
void setOnline(bool online);
signals:
void onlineChanged();
private:
QUrl m_url;
BearerType m_bearerType = BearerTypeUnknown;
bool m_secure = false;
QString m_displayName;
bool m_online = false;
};
class Connections: public QAbstractListModel

View File

@ -40,7 +40,7 @@ public:
};
Q_ENUM(DeviceRole)
explicit DiscoveryModel(QObject *parent = 0);
explicit DiscoveryModel(QObject *parent = nullptr);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;

View File

@ -20,7 +20,7 @@ NymeaDiscovery::NymeaDiscovery(QObject *parent) : QObject(parent)
m_bluetooth = new BluetoothServiceDiscovery(m_discoveryModel, this);
#endif
connect(Engine::instance()->awsClient(), &AWSClient::devicesFetched, this, &NymeaDiscovery::cloudDevicesFetched);
connect(Engine::instance()->awsClient()->awsDevices(), &AWSDevices::countChanged, this, &NymeaDiscovery::syncCloudDevices);
}
bool NymeaDiscovery::discovering() const
@ -41,6 +41,7 @@ void NymeaDiscovery::setDiscovering(bool discovering)
m_bluetooth->discover();
}
if (Engine::instance()->awsClient()->isLoggedIn()) {
syncCloudDevices();
Engine::instance()->awsClient()->fetchDevices();
}
} else {
@ -58,23 +59,26 @@ DiscoveryModel *NymeaDiscovery::discoveryModel() const
return m_discoveryModel;
}
void NymeaDiscovery::cloudDevicesFetched(const QList<AWSDevice> &devices)
void NymeaDiscovery::syncCloudDevices()
{
qDebug() << "Cloud devices fetched";
foreach (const AWSDevice &d, devices) {
DiscoveryDevice *device = m_discoveryModel->find(d.id);
for (int i = 0; i < Engine::instance()->awsClient()->awsDevices()->rowCount(); i++) {
AWSDevice *d = Engine::instance()->awsClient()->awsDevices()->get(i);
DiscoveryDevice *device = m_discoveryModel->find(d->id());
if (!device) {
device = new DiscoveryDevice();
device->setUuid(d.id);
device->setName(d.name);
device->setUuid(d->id());
device->setName(d->name());
m_discoveryModel->addDevice(device);
}
QUrl url;
url.setScheme("cloud");
url.setHost(d.id);
url.setHost(d->id());
if (!device->connections()->find(url)) {
Connection *conn = new Connection(url, Connection::BearerTypeCloud, true, d.id);
Connection *conn = new Connection(url, Connection::BearerTypeCloud, true, d->id());
conn->setOnline(d->online());
device->connections()->addConnection(conn);
}
}
}

View File

@ -28,7 +28,7 @@ signals:
void discoveringChanged();
private slots:
void cloudDevicesFetched(const QList<AWSDevice> &devices);
void syncCloudDevices();
private:
bool m_discovering = false;

View File

@ -81,6 +81,22 @@ AWSClient *Engine::awsClient() const
return m_aws;
}
void Engine::deployCertificate()
{
if (!m_jsonRpcClient->connected()) {
qWarning() << "JSONRPC not connected. Cannot deploy certificate";
return;
}
if (!m_aws->isLoggedIn()) {
qWarning() << "Not logged in at AWS. Cannot deploy certificate";
return;
}
m_aws->fetchCertificate(m_jsonRpcClient->serverUuid(), [this](const QByteArray &rootCA, const QByteArray &certificate, const QByteArray &publicKey, const QByteArray &privateKey, const QString &endpoint){
qDebug() << "Certificate received" << certificate << publicKey << privateKey;
m_jsonRpcClient->deployCertificate(rootCA, certificate, publicKey, privateKey, endpoint);
});
}
NymeaConnection *Engine::connection() const
{
return m_connection;
@ -107,6 +123,22 @@ Engine::Engine(QObject *parent) :
connect(m_jsonRpcClient, &JsonRpcClient::authenticationRequiredChanged, this, &Engine::onConnectedChanged);
connect(m_deviceManager, &DeviceManager::fetchingDataChanged, this, &Engine::onDeviceManagerFetchingChanged);
connect(m_aws, &AWSClient::devicesFetched, this, [this]() {
if (m_jsonRpcClient->connected() && m_jsonRpcClient->cloudConnectionState() == JsonRpcClient::CloudConnectionStateConnected) {
if (m_aws->awsDevices()->getDevice(m_jsonRpcClient->serverUuid()) == nullptr) {
m_jsonRpcClient->setupRemoteAccess(m_aws->idToken(), m_aws->cognitoIdentityId());
}
}
});
connect(m_jsonRpcClient, &JsonRpcClient::connectedChanged, this, [this]() {
if (m_jsonRpcClient->connected() && m_jsonRpcClient->cloudConnectionState() == JsonRpcClient::CloudConnectionStateConnected) {
if (m_aws->awsDevices()->getDevice(m_jsonRpcClient->serverUuid()) == nullptr) {
m_jsonRpcClient->setupRemoteAccess(m_aws->idToken(), m_aws->cognitoIdentityId());
}
}
});
}
void Engine::onConnectedChanged()

View File

@ -62,6 +62,8 @@ public:
BluetoothDiscovery *bluetoothDiscovery() const;
AWSClient* awsClient() const;
Q_INVOKABLE void deployCertificate();
private:
explicit Engine(QObject *parent = nullptr);
static Engine *s_instance;

View File

@ -29,6 +29,7 @@
#include <QUuid>
#include <QSettings>
#include <QVersionNumber>
#include <QMetaEnum>
JsonRpcClient::JsonRpcClient(NymeaConnection *connection, QObject *parent) :
JsonHandler(parent),
@ -120,8 +121,9 @@ void JsonRpcClient::notificationReceived(const QVariantMap &data)
}
if (data.value("notification").toString() == "JSONRPC.CloudConnectedChanged") {
m_cloudConnected = data.value("params").toMap().value("connected").toBool();
emit cloudConnectedChanged();
QMetaEnum connectionStateEnum = QMetaEnum::fromType<CloudConnectionState>();
m_cloudConnectionState = static_cast<CloudConnectionState>(connectionStateEnum.keyToValue(data.value("params").toMap().value("connectionState").toByteArray().data()));
emit cloudConnectionStateChanged();
return;
}
@ -131,8 +133,19 @@ void JsonRpcClient::notificationReceived(const QVariantMap &data)
void JsonRpcClient::isCloudConnectedReply(const QVariantMap &data)
{
qDebug() << "Cloud is connected" << data;
m_cloudConnected = data.value("params").toMap().value("connected").toBool();
emit cloudConnectedChanged();
QMetaEnum connectionStateEnum = QMetaEnum::fromType<CloudConnectionState>();
m_cloudConnectionState = static_cast<CloudConnectionState>(connectionStateEnum.keyToValue(data.value("params").toMap().value("connectionState").toByteArray().data()));
emit cloudConnectionStateChanged();
}
void JsonRpcClient::setupRemoteAccessReply(const QVariantMap &data)
{
qDebug() << "Setup Remote Access reply" << data;
}
void JsonRpcClient::deployCertificateReply(const QVariantMap &data)
{
qDebug() << "deploy certificate reply:" << data;
}
bool JsonRpcClient::connected() const
@ -155,9 +168,21 @@ bool JsonRpcClient::pushButtonAuthAvailable() const
return m_pushButtonAuthAvailable;
}
bool JsonRpcClient::cloudConnected() const
JsonRpcClient::CloudConnectionState JsonRpcClient::cloudConnectionState() const
{
return m_cloudConnected;
return m_cloudConnectionState;
}
void JsonRpcClient::deployCertificate(const QByteArray &rootCA, const QByteArray &certificate, const QByteArray &publicKey, const QByteArray &privateKey, const QString &endpoint)
{
QVariantMap params;
params.insert("rootCA", rootCA);
params.insert("certificatePEM", certificate);
params.insert("publicKey", publicKey);
params.insert("privateKey", privateKey);
params.insert("endpoint", endpoint);
sendCommand("JSONRPC.SetupCloudConnection", params, this, "deployCertificateReply");
}
QString JsonRpcClient::serverVersion() const
@ -209,6 +234,14 @@ int JsonRpcClient::requestPushButtonAuth(const QString &deviceName)
return reply->commandId();
}
void JsonRpcClient::setupRemoteAccess(const QString &idToken, const QString &userId)
{
QVariantMap params;
params.insert("idToken", idToken);
params.insert("userId", userId);
sendCommand("JSONRPC.SetupRemoteAccess", params, this, "setupRemoteAccessReply");
}
bool JsonRpcClient::ensureServerVersion(const QString &jsonRpcVersion)
{
return QVersionNumber(m_jsonRpcVersion) >= QVersionNumber::fromString(jsonRpcVersion);

View File

@ -40,13 +40,21 @@ class JsonRpcClient : public JsonHandler
Q_PROPERTY(bool initialSetupRequired READ initialSetupRequired NOTIFY initialSetupRequiredChanged)
Q_PROPERTY(bool authenticationRequired READ authenticationRequired NOTIFY authenticationRequiredChanged)
Q_PROPERTY(bool pushButtonAuthAvailable READ pushButtonAuthAvailable NOTIFY pushButtonAuthAvailableChanged)
Q_PROPERTY(bool cloudConnected READ cloudConnected NOTIFY cloudConnectedChanged)
Q_PROPERTY(CloudConnectionState cloudConnectionState READ cloudConnectionState NOTIFY cloudConnectionStateChanged)
Q_PROPERTY(QString serverVersion READ serverVersion NOTIFY handshakeReceived)
Q_PROPERTY(QString jsonRpcVersion READ jsonRpcVersion NOTIFY handshakeReceived)
Q_PROPERTY(QString serverUuid READ serverUuid NOTIFY handshakeReceived)
public:
explicit JsonRpcClient(NymeaConnection *connection, QObject *parent = 0);
enum CloudConnectionState {
CloudConnectionStateDisabled,
CloudConnectionStateUnconfigured,
CloudConnectionStateConnecting,
CloudConnectionStateConnected
};
Q_ENUM(CloudConnectionState)
explicit JsonRpcClient(NymeaConnection *connection, QObject *parent = nullptr);
QString nameSpace() const override;
@ -60,7 +68,8 @@ public:
bool initialSetupRequired() const;
bool authenticationRequired() const;
bool pushButtonAuthAvailable() const;
bool cloudConnected() const;
CloudConnectionState cloudConnectionState() const;
void deployCertificate(const QByteArray &rootCA, const QByteArray &certificate, const QByteArray &publicKey, const QByteArray &privateKey, const QString &endpoint);
QString serverVersion() const;
QString jsonRpcVersion() const;
@ -70,6 +79,7 @@ public:
Q_INVOKABLE int createUser(const QString &username, const QString &password);
Q_INVOKABLE int authenticate(const QString &username, const QString &password, const QString &deviceName);
Q_INVOKABLE int requestPushButtonAuth(const QString &deviceName);
Q_INVOKABLE void setupRemoteAccess(const QString &idToken, const QString &userId);
Q_INVOKABLE bool ensureServerVersion(const QString &jsonRpcVersion);
@ -84,7 +94,7 @@ signals:
void authenticationFailed();
void pushButtonAuthFailed();
void createUserFailed(const QString &error);
void cloudConnectedChanged();
void cloudConnectionStateChanged();
void responseReceived(const int &commandId, const QVariantMap &response);
@ -105,7 +115,7 @@ private:
bool m_initialSetupRequired = false;
bool m_authenticationRequired = false;
bool m_pushButtonAuthAvailable = false;
bool m_cloudConnected = false;
CloudConnectionState m_cloudConnectionState = CloudConnectionStateDisabled;
int m_pendingPushButtonTransaction = -1;
QString m_serverUuid;
QVersionNumber m_jsonRpcVersion;
@ -124,6 +134,8 @@ private:
Q_INVOKABLE void setNotificationsEnabledResponse(const QVariantMap &params);
Q_INVOKABLE void notificationReceived(const QVariantMap &data);
Q_INVOKABLE void isCloudConnectedReply(const QVariantMap &data);
Q_INVOKABLE void setupRemoteAccessReply(const QVariantMap &data);
Q_INVOKABLE void deployCertificateReply(const QVariantMap &data);
void sendRequest(const QVariantMap &request);

View File

@ -174,6 +174,7 @@ void registerQmlTypes() {
qmlRegisterUncreatableType<WirelessAccessPointsProxy>(uri, 1, 0, "WirelessAccessPoints", "Can't create this in QML. Get it from the Engine instance.");
qmlRegisterUncreatableType<AWSClient>(uri, 1, 0, "AWSClient", "Can't create this in QML. Get it from Engine");
qmlRegisterUncreatableType<AWSDevice>(uri, 1, 0, "AWSDevice", "Can't create this in QML. Get it from AWSClient");
qmlRegisterType<RuleTemplates>(uri, 1, 0, "RuleTemplates");
qmlRegisterType<RuleTemplatesFilterModel>(uri, 1, 0, "RuleTemplatesFilterModel");

View File

@ -243,5 +243,7 @@
<file>ui/images/cloud.svg</file>
<file>ui/system/CloudSettingsPage.qml</file>
<file>ui/connection/CloudLoginPage.qml</file>
<file>ui/images/cloud-offline.svg</file>
<file>ui/images/cloud-error.svg</file>
</qresource>
</RCC>

View File

@ -110,6 +110,12 @@ Page {
}
}
ThinDivider {}
MeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("Cloud login")
iconName: "../images/cloud.svg"
onClicked: pageStack.push(Qt.resolvedUrl("connection/CloudLoginPage.qml"))
}
MeaListItemDelegate {
Layout.fillWidth: true
text: qsTr("About %1").arg(app.appName)

View File

@ -369,6 +369,5 @@ ApplicationWindow {
KeyboardLoader {
anchors { left: parent.left; bottom: parent.bottom; right: parent.right }
}
}

View File

@ -16,16 +16,44 @@ Page {
visible: Engine.awsClient.isLoggedIn
Label {
Layout.fillWidth: true
Layout.margins: app.margins
Layout.topMargin: app.margins
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
wrapMode: Text.WordWrap
text: qsTr("Logged in as %1").arg(Engine.awsClient.username)
}
Button {
Layout.fillWidth: true
Layout.margins: app.margins
text: qsTr("Log out")
onClicked: Engine.awsClient.logout();
}
ThinDivider {}
Label {
Layout.fillWidth: true
Layout.topMargin: app.margins
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
wrapMode: Text.WordWrap
text: Engine.awsClient.awsDevices.count === 0 ?
qsTr("There are no boxes connected to your cloud yet.") :
qsTr("There (are|is) %1 boxe(s) connected to your cloud", "", Engine.awsClient.awsDevices.count)
}
Repeater {
model: Engine.awsClient.awsDevices
delegate: MeaListItemDelegate {
Layout.fillWidth: true
text: model.name
subText: model.uuid
progressive: false
prominentSubText: false
iconName: "../images/cloud.svg"
secondaryIconName: model.online ? "../images/cloud.svg" : "../images/cloud-offline.svg"
}
}
}
ColumnLayout {

View File

@ -86,11 +86,19 @@ Page {
model: ListModel {
ListElement { iconSource: "../images/network-vpn.svg"; text: qsTr("Manual connection"); page: "ManualConnectPage.qml" }
ListElement { iconSource: "../images/bluetooth.svg"; text: qsTr("Wireless setup"); page: "BluetoothDiscoveryPage.qml"; }
ListElement { iconSource: "../images/cloud.svg"; text: qsTr("Cloud login"); page: "CloudLoginPage.qml" }
ListElement { iconSource: "../images/private-browsing.svg"; text: qsTr("Demo mode"); page: "" }
ListElement { iconSource: "../images/stock_application.svg"; text: qsTr("App settings"); page: "../AppSettingsPage.qml" }
}
onClicked: {
pageStack.push(model.get(index).page);
if (index === 2) {
Engine.connection.connect("nymea://nymea.nymea.io:2222")
var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml"))
page.cancel.connect(function() {
Engine.connection.disconnect()
})
} else {
pageStack.push(model.get(index).page);
}
}
}
@ -261,23 +269,32 @@ Page {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
visible: discovery.discoveryModel.count === 0
// visible: discovery.discoveryModel.count === 0
text: qsTr("Start wireless setup")
onClicked: pageStack.push(Qt.resolvedUrl("connection/BluetoothDiscoveryPage.qml"))
onClicked: pageStack.push(Qt.resolvedUrl("BluetoothDiscoveryPage.qml"))
}
Button {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: qsTr("Cloud login")
visible: !Engine.awsClient.isLoggedIn
onClicked: pageStack.push(Qt.resolvedUrl("CloudLoginPage.qml"))
}
Button {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
Layout.bottomMargin: app.margins
visible: discovery.discoveryModel.count === 0
// visible: discovery.discoveryModel.count === 0
text: qsTr("Demo mode (online)")
onClicked: {
Engine.connection.connect("nymea://nymea.nymea.io:2222")
var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml"))
page.cancel.connect(function() {
Engine.connection.disconnect()
})
Engine.connection.connect("nymea://nymea.nymea.io:2222")
}
}

View File

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="90"
height="90"
id="svg6138"
version="1.1"
inkscape:version="0.91+devel r"
viewBox="0 0 90 90.000001"
sodipodi:docname="sync-error.svg">
<defs
id="defs6140" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.6077032"
inkscape:cx="-148.24923"
inkscape:cy="56.003297"
inkscape:document-units="px"
inkscape:current-layer="g6253"
showgrid="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-global="true"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-others="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true">
<inkscape:grid
type="xygrid"
id="grid6700"
empspacing="6" />
<sodipodi:guide
orientation="0,1"
position="62,87"
id="guide4084" />
<sodipodi:guide
orientation="0,1"
position="63,84"
id="guide4086" />
<sodipodi:guide
orientation="0,1"
position="63,81"
id="guide4088" />
<sodipodi:guide
orientation="1,0"
position="3,70"
id="guide4090" />
<sodipodi:guide
orientation="1,0"
position="6,66"
id="guide4092" />
<sodipodi:guide
orientation="1,0"
position="9,59"
id="guide4094" />
<sodipodi:guide
orientation="1,0"
position="87,63"
id="guide4096" />
<sodipodi:guide
orientation="1,0"
position="84,64"
id="guide4098" />
<sodipodi:guide
orientation="1,0"
position="81,55"
id="guide4100" />
<sodipodi:guide
orientation="0,1"
position="60,3"
id="guide4102" />
<sodipodi:guide
orientation="0,1"
position="61,6"
id="guide4104" />
<sodipodi:guide
orientation="0,1"
position="62,9"
id="guide4106" />
</sodipodi:namedview>
<metadata
id="metadata6143">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-283.57144,-358.79068)">
<g
id="g6253"
inkscape:export-filename="planemode01.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
transform="matrix(-1,0,0,1,547.57143,-1341.5715)">
<rect
style="fill:none;stroke:none"
id="rect6257"
width="90"
height="90"
x="174"
y="1700.3622" />
<path
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.99999988"
d="m 221.99999,1732.3622 0,8.5799 c 0,2.5213 -0.0933,4.8561 -0.2793,7.0086 -0.18602,2.1215 -0.43607,4.2591 -0.74609,6.4114 l -3.99805,0 c -0.27903,-2.1523 -0.51126,-4.2899 -0.69726,-6.4114 -0.18602,-2.1217 -0.2793,-4.4565 -0.2793,-7.0086 l 0,-8.5799 6,0 z"
id="path4185"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;display:inline;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.99999994"
d="m 218.99999,1758.3622 c 1.10006,0 2.04956,0.3867 2.84961,1.1582 0.76673,0.738 1.15039,1.6912 1.15039,2.8653 0,1.174 -0.38366,2.1311 -1.15039,2.8691 -0.80005,0.738 -1.74955,1.1074 -2.84961,1.1074 -1.13341,0 -2.08291,-0.3694 -2.84961,-1.1074 -0.76673,-0.738 -1.15039,-1.6951 -1.15039,-2.8691 0,-1.1741 0.38366,-2.1273 1.15039,-2.8653 0.7667,-0.7715 1.7162,-1.1582 2.84961,-1.1582 z"
id="path4942" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 212.5,1714.3613 c -11.29815,0 -20.5,9.2019 -20.5,20.5 l 0,0.01 0,0.01 c 0.002,0.2497 0.0454,0.4967 0.0566,0.7461 -10.15029,1.2279 -18.0494,9.7961 -18.05664,20.2363 0,11.2982 9.20185,20.5 20.5,20.5 l 49,0 c 11.29815,0 20.5,-9.2018 20.5,-20.5 l 0,-0 c -0.0116,-8.9301 -5.844,-16.696 -14.21094,-19.4063 0.0644,-0.5317 0.21041,-1.0527 0.21094,-1.5898 l 0,-0 c 0,-7.4321 -6.06785,-13.5 -13.5,-13.5 l -0.002,0 -0.002,0 c -2.55665,0 -4.93658,0.9271 -7.07421,2.2754 -3.76777,-5.6712 -10.02107,-9.2653 -16.91993,-9.2754 l -0.002,0 z m -0.002,4 0.002,0 c 6.1355,0.01 11.75025,3.4089 14.59766,8.8418 l 1.18945,2.2676 1.91211,-1.7031 c 1.73553,-1.5457 3.97683,-2.4018 6.30078,-2.4063 5.27039,0 9.5,4.2296 9.5,9.5 -0.001,0.8309 -0.11279,1.6591 -0.33008,2.4629 l -0.54297,2.0117 2.03125,0.461 c 7.51333,1.7082 12.82999,8.3597 12.8418,16.0644 0,7e-4 0,0 0,0 0,6e-4 0,0 0,0 -0.002,9.1346 -7.36493,16.4961 -16.5,16.4961 l -49,0 c -9.13573,0 -16.49893,-7.3625 -16.5,-16.498 l 0,-0 c 0.007,-9.023 7.2045,-16.3348 16.22656,-16.4843 l 2.27539,-0.037 -0.33007,-2.25 c -0.10801,-0.7397 -0.16511,-1.4866 -0.17188,-2.2343 0.003,-9.133 7.36456,-16.4931 16.49805,-16.4942 z"
id="path4154"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="90"
height="90"
id="svg6138"
version="1.1"
inkscape:version="0.91+devel r"
viewBox="0 0 90 90.000001"
sodipodi:docname="sync-offline.svg">
<defs
id="defs6140" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.0931703"
inkscape:cx="-81.39135"
inkscape:cy="23.804422"
inkscape:document-units="px"
inkscape:current-layer="g6253"
showgrid="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-global="true"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-others="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true">
<inkscape:grid
type="xygrid"
id="grid6700"
empspacing="6" />
<sodipodi:guide
orientation="0,1"
position="62,87"
id="guide4084" />
<sodipodi:guide
orientation="0,1"
position="63,84"
id="guide4086" />
<sodipodi:guide
orientation="0,1"
position="63,81"
id="guide4088" />
<sodipodi:guide
orientation="1,0"
position="3,70"
id="guide4090" />
<sodipodi:guide
orientation="1,0"
position="6,66"
id="guide4092" />
<sodipodi:guide
orientation="1,0"
position="9,59"
id="guide4094" />
<sodipodi:guide
orientation="1,0"
position="87,63"
id="guide4096" />
<sodipodi:guide
orientation="1,0"
position="84,64"
id="guide4098" />
<sodipodi:guide
orientation="1,0"
position="81,55"
id="guide4100" />
<sodipodi:guide
orientation="0,1"
position="60,3"
id="guide4102" />
<sodipodi:guide
orientation="0,1"
position="61,6"
id="guide4104" />
<sodipodi:guide
orientation="0,1"
position="62,9"
id="guide4106" />
</sodipodi:namedview>
<metadata
id="metadata6143">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-283.57144,-358.79068)">
<g
id="g6253"
inkscape:export-filename="planemode01.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
transform="matrix(-1,0,0,1,547.57143,-1341.5715)">
<rect
style="fill:none;stroke:none"
id="rect6257"
width="90"
height="90"
x="174"
y="1700.3622" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:none;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.5;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 212.5,1714.3613 c -11.29815,0 -20.5,9.2019 -20.5,20.5 l 0,0.01 0,0.01 c 0.002,0.2497 0.0454,0.4967 0.0566,0.7461 -10.15029,1.2279 -18.0494,9.7961 -18.05664,20.2363 0,11.2982 9.20185,20.5 20.5,20.5 l 49,0 c 11.29815,0 20.5,-9.2018 20.5,-20.5 l 0,-0 c -0.0116,-8.9301 -5.844,-16.696 -14.21094,-19.4063 0.0644,-0.5317 0.21041,-1.0527 0.21094,-1.5898 l 0,-0 c 0,-7.4321 -6.06785,-13.5 -13.5,-13.5 l -0.002,0 -0.002,0 c -2.55665,0 -4.93658,0.9271 -7.07421,2.2754 -3.76777,-5.6712 -10.02107,-9.2653 -16.91993,-9.2754 l -0.002,0 z m -0.002,4 0.002,0 c 6.1355,0.01 11.75025,3.4089 14.59766,8.8418 l 1.18945,2.2676 1.91211,-1.7031 c 1.73553,-1.5457 3.97683,-2.4018 6.30078,-2.4063 5.27039,0 9.5,4.2296 9.5,9.5 -0.001,0.8309 -0.11279,1.6591 -0.33008,2.4629 l -0.54297,2.0117 2.03125,0.461 c 7.51333,1.7082 12.82999,8.3597 12.8418,16.0644 0,7e-4 0,0 0,0 0,6e-4 0,0 0,0 -0.002,9.1346 -7.36493,16.4961 -16.5,16.4961 l -49,0 c -9.13573,0 -16.49893,-7.3625 -16.5,-16.498 l 0,-0 c 0.007,-9.023 7.2045,-16.3348 16.22656,-16.4843 l 2.27539,-0.037 -0.33007,-2.25 c -0.10801,-0.7397 -0.16511,-1.4866 -0.17188,-2.2343 0.003,-9.133 7.36456,-16.4931 16.49805,-16.4942 z"
id="path4154"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -11,12 +11,35 @@ Page {
onBackPressed: pageStack.pop();
}
Connections {
target: Engine.basicConfiguration
onCloudEnabledChanged: {
if (Engine.jsonRpcClient.cloudConnectionState === JsonRpcClient.CloudConnectionStateUnconfigured) {
Engine.deployCertificate();
}
}
}
Connections {
target: Engine.jsonRpcClient
onCloudConnectionStateChanged: {
if (Engine.awsClient.isLoggedIn && Engine.awsClient.awsDevices.getDevice(Engine.jsonRpcClient.serverUuid) === null) {
print("Pairing user and box...")
Engine.jsonRpcClient.setupRemoteAccess(Engine.awsClient.idToken, Engine.awsClient.userId);
}
}
}
ColumnLayout {
anchors { left: parent.left; top: parent.top; right: parent.right }
Label {
Layout.fillWidth: true
text: Engine.jsonRpcClient.cloudConnected
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
Layout.topMargin: app.margins
text: qsTr("You can connect a nymea:box to a nymea:cloud in order to access it from anywhere")
wrapMode: Text.WordWrap
}
SwitchDelegate {
@ -27,5 +50,45 @@ Page {
Engine.basicConfiguration.cloudEnabled = checked;
}
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
visible: Engine.basicConfiguration.cloudEnabled
BusyIndicator {
visible: Engine.jsonRpcClient.cloudConnectionState == JsonRpcClient.CloudConnectionStateUnconfigured ||
Engine.jsonRpcClient.cloudConnectionState == JsonRpcClient.CloudConnectionStateConnecting
}
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: {
switch (Engine.jsonRpcClient.cloudConnectionState) {
case JsonRpcClient.CloudConnectionStateDisabled:
return ""
case JsonRpcClient.CloudConnectionStateUnconfigured:
return qsTr("Configuring the box to connect to nymea:cloud...");
case JsonRpcClient.CloudConnectionStateConnecting:
return qsTr("Connecting the box to nymea:cloud...");
case JsonRpcClient.CloudConnectionStateConnected:
return qsTr("The box is connected to nymea:cloud.");
}
return Engine.jsonRpcClient.cloudConnectionState
}
}
}
// Label {
// Layout.fillWidth: true
// Layout.leftMargin: app.margins
// Layout.rightMargin: app.margins
// visible: Engine.basicConfiguration.cloudEnabled && Engine.awsClient.isLoggedIn
// text: Engine.awsClient.awsDevices.getDevice(Engine.jsonRpcClient.serverUuid) !== null ?
// qsTr("This box is connected to a nymea:cloud.") :
// qsTr("Connecting to nymea:cloud...")
// }
}
}

@ -1 +1 @@
Subproject commit 0a18897de79606543b0e7a68ac9b1990a5dd761c
Subproject commit f6e2d9b3b208362159dd22a4eb11527db25d8760