Merge PR #138: Rework how hosts are remembered and connected to
This commit is contained in:
commit
557aff7fef
@ -7,6 +7,7 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include "sigv4utils.h"
|
#include "sigv4utils.h"
|
||||||
|
|
||||||
@ -225,10 +226,10 @@ void AWSClient::login(const QString &username, const QString &password, int atte
|
|||||||
m_idToken = authenticationResult.value("IdToken").toByteArray();
|
m_idToken = authenticationResult.value("IdToken").toByteArray();
|
||||||
m_refreshToken = authenticationResult.value("RefreshToken").toByteArray();
|
m_refreshToken = authenticationResult.value("RefreshToken").toByteArray();
|
||||||
|
|
||||||
qDebug() << "AWS ID token" << m_idToken;
|
// qDebug() << "AWS ID token" << m_idToken;
|
||||||
QList<QByteArray> jwtParts = m_idToken.split('.');
|
QList<QByteArray> jwtParts = m_idToken.split('.');
|
||||||
if (jwtParts.count() != 3) {
|
if (jwtParts.count() != 3) {
|
||||||
qWarning() << "JWT token doesn't have 3 parts";
|
qWarning() << "Error: JWT token doesn't have 3 parts. Cannot retrieve AWS Cognito ID.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// qDebug() << "decoded header:" << QByteArray::fromBase64(jwtParts.at(0));
|
// qDebug() << "decoded header:" << QByteArray::fromBase64(jwtParts.at(0));
|
||||||
@ -236,7 +237,7 @@ void AWSClient::login(const QString &username, const QString &password, int atte
|
|||||||
QJsonDocument tokenPayloadJsonDoc = QJsonDocument::fromJson(QByteArray::fromBase64(jwtParts.at(1)));
|
QJsonDocument tokenPayloadJsonDoc = QJsonDocument::fromJson(QByteArray::fromBase64(jwtParts.at(1)));
|
||||||
m_userId = tokenPayloadJsonDoc.toVariant().toMap().value("cognito:username").toByteArray();
|
m_userId = tokenPayloadJsonDoc.toVariant().toMap().value("cognito:username").toByteArray();
|
||||||
|
|
||||||
qDebug() << "Getting cognito ID";
|
// qDebug() << "Getting cognito ID";
|
||||||
getId();
|
getId();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -596,7 +597,7 @@ void AWSClient::getId()
|
|||||||
}
|
}
|
||||||
m_identityId = jsonDoc.toVariant().toMap().value("IdentityId").toByteArray();
|
m_identityId = jsonDoc.toVariant().toMap().value("IdentityId").toByteArray();
|
||||||
|
|
||||||
qDebug() << "Received cognito identity id" << m_identityId;// << qUtf8Printable(data);
|
// qDebug() << "Received cognito identity id" << m_identityId;// << qUtf8Printable(data);
|
||||||
getCredentialsForIdentity(m_identityId);
|
getCredentialsForIdentity(m_identityId);
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -866,25 +867,30 @@ bool AWSClient::postToMQTT(const QString &boxId, const QString ×tamp, std::
|
|||||||
request.setUrl("https://" + m_configs.value(m_usedConfig).mqttEndpoint + path1);
|
request.setUrl("https://" + m_configs.value(m_usedConfig).mqttEndpoint + path1);
|
||||||
|
|
||||||
qDebug() << "Posting to MQTT:" << request.url().toString();
|
qDebug() << "Posting to MQTT:" << request.url().toString();
|
||||||
qDebug() << "HEADERS:";
|
// qDebug() << "HEADERS:";
|
||||||
foreach (const QByteArray &headerName, request.rawHeaderList()) {
|
// foreach (const QByteArray &headerName, request.rawHeaderList()) {
|
||||||
qDebug() << headerName << ":" << request.rawHeader(headerName);
|
// qDebug() << headerName << ":" << request.rawHeader(headerName);
|
||||||
}
|
// }
|
||||||
qDebug() << "Payload:" << payload;
|
// qDebug() << "Payload:" << payload;
|
||||||
QNetworkReply *reply = m_nam->post(request, payload);
|
QNetworkReply *reply = m_nam->post(request, payload);
|
||||||
|
QTimer::singleShot(5000, reply, [reply, callback](){
|
||||||
|
reply->deleteLater();
|
||||||
|
qWarning() << "Timeout posting to MQTT";
|
||||||
|
callback(false);
|
||||||
|
});
|
||||||
connect(reply, &QNetworkReply::finished, this, [reply, callback]() {
|
connect(reply, &QNetworkReply::finished, this, [reply, callback]() {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
QByteArray data = reply->readAll();
|
QByteArray data = reply->readAll();
|
||||||
qDebug() << "post reply" << data;
|
// qDebug() << "MQTT post reply" << data;
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
qWarning() << "Network reply error" << reply->error() << reply->errorString();
|
qWarning() << "MQTT Network reply error" << reply->error() << reply->errorString();
|
||||||
callback(false);
|
callback(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QJsonParseError error;
|
QJsonParseError error;
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||||
if (error.error != QJsonParseError::NoError) {
|
if (error.error != QJsonParseError::NoError) {
|
||||||
qWarning() << "Failed to parse reply" << error.error << error.errorString() << data;
|
qWarning() << "Failed to parse MQTT reply" << error.error << error.errorString() << data;
|
||||||
callback(false);
|
callback(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,7 @@ bool BluetoothTransport::connect(const QUrl &url)
|
|||||||
qWarning() << "BluetoothInterface: Cannot connect. Invalid scheme in url" << url.toString();
|
qWarning() << "BluetoothInterface: Cannot connect. Invalid scheme in url" << url.toString();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
m_url = url;
|
||||||
|
|
||||||
QUrlQuery query(url);
|
QUrlQuery query(url);
|
||||||
QString macAddressString = query.queryItemValue("mac");
|
QString macAddressString = query.queryItemValue("mac");
|
||||||
@ -54,6 +55,11 @@ bool BluetoothTransport::connect(const QUrl &url)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl BluetoothTransport::url() const
|
||||||
|
{
|
||||||
|
return m_url;
|
||||||
|
}
|
||||||
|
|
||||||
void BluetoothTransport::disconnect()
|
void BluetoothTransport::disconnect()
|
||||||
{
|
{
|
||||||
m_socket->close();
|
m_socket->close();
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
#define BLUETOOTHTRANSPORT_H
|
#define BLUETOOTHTRANSPORT_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QUrl>
|
||||||
#include <QBluetoothSocket>
|
#include <QBluetoothSocket>
|
||||||
|
|
||||||
#include "nymeatransportinterface.h"
|
#include "nymeatransportinterface.h"
|
||||||
@ -42,11 +43,13 @@ public:
|
|||||||
explicit BluetoothTransport(QObject *parent = nullptr);
|
explicit BluetoothTransport(QObject *parent = nullptr);
|
||||||
|
|
||||||
bool connect(const QUrl &url) override;
|
bool connect(const QUrl &url) override;
|
||||||
|
QUrl url() const override;
|
||||||
void disconnect() override;
|
void disconnect() override;
|
||||||
ConnectionState connectionState() const override;
|
ConnectionState connectionState() const override;
|
||||||
void sendData(const QByteArray &data) override;
|
void sendData(const QByteArray &data) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QUrl m_url;
|
||||||
QBluetoothSocket *m_socket = nullptr;
|
QBluetoothSocket *m_socket = nullptr;
|
||||||
QBluetoothServiceInfo m_service;
|
QBluetoothServiceInfo m_service;
|
||||||
|
|
||||||
|
|||||||
@ -49,10 +49,12 @@ bool CloudTransport::connect(const QUrl &url)
|
|||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Connecting to" << url;
|
qDebug() << "Connecting to" << url;
|
||||||
|
m_url = url;
|
||||||
|
|
||||||
m_timestamp = QDateTime::currentDateTime();
|
m_timestamp = QDateTime::currentDateTime();
|
||||||
bool postResult = m_awsClient->postToMQTT(url.host(), QString::number(m_timestamp.toMSecsSinceEpoch()), [this](bool success) {
|
bool postResult = m_awsClient->postToMQTT(url.host(), QString::number(m_timestamp.toMSecsSinceEpoch()), [this](bool success) {
|
||||||
if (success) {
|
if (success) {
|
||||||
|
qDebug() << "MQTT Post done. Connecting to remote proxy";
|
||||||
m_remoteproxyConnection->connectServer(QUrl("wss://remoteproxy.nymea.io"));
|
m_remoteproxyConnection->connectServer(QUrl("wss://remoteproxy.nymea.io"));
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Posting to MQTT failed";
|
qDebug() << "Posting to MQTT failed";
|
||||||
@ -68,6 +70,11 @@ bool CloudTransport::connect(const QUrl &url)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl CloudTransport::url() const
|
||||||
|
{
|
||||||
|
return m_url;
|
||||||
|
}
|
||||||
|
|
||||||
void CloudTransport::disconnect()
|
void CloudTransport::disconnect()
|
||||||
{
|
{
|
||||||
qDebug() << "CloudTransport: Disconnecting from server.";
|
qDebug() << "CloudTransport: Disconnecting from server.";
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include "nymeatransportinterface.h"
|
#include "nymeatransportinterface.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
class AWSClient;
|
class AWSClient;
|
||||||
namespace remoteproxyclient {
|
namespace remoteproxyclient {
|
||||||
@ -25,12 +26,14 @@ public:
|
|||||||
explicit CloudTransport(AWSClient *awsClient, QObject *parent = nullptr);
|
explicit CloudTransport(AWSClient *awsClient, QObject *parent = nullptr);
|
||||||
|
|
||||||
bool connect(const QUrl &url) override;
|
bool connect(const QUrl &url) override;
|
||||||
|
QUrl url() const override;
|
||||||
void disconnect() override;
|
void disconnect() override;
|
||||||
ConnectionState connectionState() const override;
|
ConnectionState connectionState() const override;
|
||||||
void sendData(const QByteArray &data) override;
|
void sendData(const QByteArray &data) override;
|
||||||
|
|
||||||
void ignoreSslErrors(const QList<QSslError> &errors) override;
|
void ignoreSslErrors(const QList<QSslError> &errors) override;
|
||||||
private:
|
private:
|
||||||
|
QUrl m_url;
|
||||||
AWSClient *m_awsClient = nullptr;
|
AWSClient *m_awsClient = nullptr;
|
||||||
remoteproxyclient::RemoteProxyConnection *m_remoteproxyConnection = nullptr;
|
remoteproxyclient::RemoteProxyConnection *m_remoteproxyConnection = nullptr;
|
||||||
QDateTime m_timestamp;
|
QDateTime m_timestamp;
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
#include "bluetoothservicediscovery.h"
|
#include "bluetoothservicediscovery.h"
|
||||||
|
|
||||||
#include "discoverymodel.h"
|
#include "../nymeahosts.h"
|
||||||
#include "discoverydevice.h"
|
#include "../nymeahost.h"
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
BluetoothServiceDiscovery::BluetoothServiceDiscovery(DiscoveryModel *discoveryModel, QObject *parent) :
|
BluetoothServiceDiscovery::BluetoothServiceDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
m_discoveryModel(discoveryModel)
|
m_nymeaHosts(nymeaHosts)
|
||||||
{
|
{
|
||||||
m_nymeaServiceUuid = QBluetoothUuid(QUuid("997936b5-d2cd-4c57-b41b-c6048320cd2b"));
|
m_nymeaServiceUuid = QBluetoothUuid(QUuid("997936b5-d2cd-4c57-b41b-c6048320cd2b"));
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ bool BluetoothServiceDiscovery::available() const
|
|||||||
if (!m_localDevice)
|
if (!m_localDevice)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return m_localDevice->isValid() && !m_localDevice->hostMode() != QBluetoothLocalDevice::HostPoweredOff;
|
return m_localDevice->isValid() && m_localDevice->hostMode() != QBluetoothLocalDevice::HostPoweredOff;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothServiceDiscovery::discover()
|
void BluetoothServiceDiscovery::discover()
|
||||||
@ -101,15 +101,15 @@ void BluetoothServiceDiscovery::onServiceDiscovered(const QBluetoothServiceInfo
|
|||||||
if (serviceInfo.serviceClassUuids().first() == QBluetoothUuid(QUuid("997936b5-d2cd-4c57-b41b-c6048320cd2b"))) {
|
if (serviceInfo.serviceClassUuids().first() == QBluetoothUuid(QUuid("997936b5-d2cd-4c57-b41b-c6048320cd2b"))) {
|
||||||
qDebug() << "BluetoothServiceDiscovery: Found nymea rfcom service!";
|
qDebug() << "BluetoothServiceDiscovery: Found nymea rfcom service!";
|
||||||
|
|
||||||
// DiscoveryDevice* device = m_discoveryModel->find(serviceInfo.device().address());
|
// NymeaHost* host = m_nymeaHosts->find(serviceInfo.device().address());
|
||||||
// if (!device) {
|
// if (!host) {
|
||||||
// device = new DiscoveryDevice(DiscoveryDevice::DeviceTypeBluetooth, this);
|
// host = new DiscoveryDevice(DiscoveryDevice::DeviceTypeBluetooth, this);
|
||||||
// qDebug() << "BluetoothServiceDiscovery: Adding new bluetooth host to model";
|
// qDebug() << "BluetoothServiceDiscovery: Adding new bluetooth host to model";
|
||||||
// device->setName(QString("%1 (%2)").arg(serviceInfo.serviceName()).arg(serviceInfo.device().name()));
|
// host->setName(QString("%1 (%2)").arg(serviceInfo.serviceName()).arg(serviceInfo.device().name()));
|
||||||
//// device->setBluetoothAddress(serviceInfo.device().address());
|
//// device->setBluetoothAddress(serviceInfo.device().address());
|
||||||
// PortConfig pc;
|
// PortConfig pc;
|
||||||
|
|
||||||
// m_discoveryModel->addDevice(device);
|
// m_nymeaHosts->addHost(device);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6,13 +6,13 @@
|
|||||||
#include <QBluetoothLocalDevice>
|
#include <QBluetoothLocalDevice>
|
||||||
#include <QBluetoothServiceDiscoveryAgent>
|
#include <QBluetoothServiceDiscoveryAgent>
|
||||||
|
|
||||||
class DiscoveryModel;
|
class NymeaHosts;
|
||||||
|
|
||||||
class BluetoothServiceDiscovery : public QObject
|
class BluetoothServiceDiscovery : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit BluetoothServiceDiscovery(DiscoveryModel *discoveryModel, QObject *parent = nullptr);
|
explicit BluetoothServiceDiscovery(NymeaHosts *nymeaHosts, QObject *parent = nullptr);
|
||||||
|
|
||||||
bool discovering() const;
|
bool discovering() const;
|
||||||
bool available() const;
|
bool available() const;
|
||||||
@ -21,7 +21,7 @@ public:
|
|||||||
Q_INVOKABLE void stopDiscovery();
|
Q_INVOKABLE void stopDiscovery();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DiscoveryModel *m_discoveryModel = nullptr;
|
NymeaHosts *m_nymeaHosts = nullptr;
|
||||||
QBluetoothLocalDevice *m_localDevice = nullptr;
|
QBluetoothLocalDevice *m_localDevice = nullptr;
|
||||||
QBluetoothServiceDiscoveryAgent *m_serviceDiscovery = nullptr;
|
QBluetoothServiceDiscoveryAgent *m_serviceDiscovery = nullptr;
|
||||||
QBluetoothUuid m_nymeaServiceUuid;
|
QBluetoothUuid m_nymeaServiceUuid;
|
||||||
227
libnymea-app-core/connection/discovery/nymeadiscovery.cpp
Normal file
227
libnymea-app-core/connection/discovery/nymeadiscovery.cpp
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
#include "nymeadiscovery.h"
|
||||||
|
#include "upnpdiscovery.h"
|
||||||
|
#include "zeroconfdiscovery.h"
|
||||||
|
#include "bluetoothservicediscovery.h"
|
||||||
|
#include "connection/awsclient.h"
|
||||||
|
#include "../nymeahost.h"
|
||||||
|
|
||||||
|
#include <QUuid>
|
||||||
|
#include <QBluetoothUuid>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QNetworkConfigurationManager>
|
||||||
|
#include <QNetworkSession>
|
||||||
|
|
||||||
|
NymeaDiscovery::NymeaDiscovery(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
m_nymeaHosts = new NymeaHosts(this);
|
||||||
|
|
||||||
|
loadFromDisk();
|
||||||
|
|
||||||
|
m_upnp = new UpnpDiscovery(m_nymeaHosts, this);
|
||||||
|
m_zeroConf = new ZeroconfDiscovery(m_nymeaHosts, this);
|
||||||
|
|
||||||
|
#ifndef Q_OS_IOS
|
||||||
|
m_bluetooth = new BluetoothServiceDiscovery(m_nymeaHosts, this);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_cloudPollTimer.setInterval(5000);
|
||||||
|
connect(&m_cloudPollTimer, &QTimer::timeout, this, [this](){
|
||||||
|
if (m_awsClient && m_awsClient->isLoggedIn()) {
|
||||||
|
m_awsClient->fetchDevices();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NymeaDiscovery::~NymeaDiscovery()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NymeaDiscovery::discovering() const
|
||||||
|
{
|
||||||
|
return m_discovering;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaDiscovery::setDiscovering(bool discovering)
|
||||||
|
{
|
||||||
|
if (m_discovering == discovering)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_discovering = discovering;
|
||||||
|
// If we have zeroconf skip upnp. ZeroConf will not do an active discovery and if it's available it'll always have good data
|
||||||
|
if (!m_zeroConf->available()) {
|
||||||
|
if (discovering) {
|
||||||
|
m_upnp->discover();
|
||||||
|
} else {
|
||||||
|
m_upnp->stopDiscovery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (discovering) {
|
||||||
|
// If there's no Zeroconf, use UPnP instead
|
||||||
|
if (!m_zeroConf->available()) {
|
||||||
|
m_upnp->discover();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always start Bluetooth discovery if HW is available
|
||||||
|
if (m_bluetooth) {
|
||||||
|
m_bluetooth->discover();
|
||||||
|
}
|
||||||
|
|
||||||
|
// start polling cloud
|
||||||
|
m_cloudPollTimer.start();
|
||||||
|
// If we're logged in, poll right away
|
||||||
|
if (m_awsClient && m_awsClient->isLoggedIn()) {
|
||||||
|
m_awsClient->fetchDevices();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!m_zeroConf->available()) {
|
||||||
|
m_upnp->stopDiscovery();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_bluetooth) {
|
||||||
|
m_bluetooth->stopDiscovery();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_cloudPollTimer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit discoveringChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
NymeaHosts *NymeaDiscovery::nymeaHosts() const
|
||||||
|
{
|
||||||
|
return m_nymeaHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
AWSClient *NymeaDiscovery::awsClient() const
|
||||||
|
{
|
||||||
|
return m_awsClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaDiscovery::setAwsClient(AWSClient *awsClient)
|
||||||
|
{
|
||||||
|
if (m_awsClient != awsClient) {
|
||||||
|
m_awsClient = awsClient;
|
||||||
|
emit awsClientChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_awsClient) {
|
||||||
|
m_awsClient->fetchDevices();
|
||||||
|
connect(m_awsClient, &AWSClient::devicesFetched, this, &NymeaDiscovery::syncCloudDevices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaDiscovery::cacheHost(NymeaHost *host)
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
settings.beginGroup("HostCache");
|
||||||
|
settings.remove(host->uuid().toString());
|
||||||
|
settings.beginGroup(host->uuid().toString());
|
||||||
|
settings.setValue("name", host->name());
|
||||||
|
QList<Connection*> connections;
|
||||||
|
Connection *remoteConnection = host->connections()->bestMatch(Connection::BearerTypeCloud);
|
||||||
|
if (remoteConnection) {
|
||||||
|
connections.append(remoteConnection);
|
||||||
|
}
|
||||||
|
Connection *lanConnection = host->connections()->bestMatch(Connection::BearerTypeWifi | Connection::BearerTypeEthernet);
|
||||||
|
if (lanConnection) {
|
||||||
|
connections.append(lanConnection);
|
||||||
|
}
|
||||||
|
Connection *btConnection = host->connections()->bestMatch(Connection::BearerTypeBluetooth);
|
||||||
|
if (btConnection) {
|
||||||
|
connections.append(btConnection);
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
foreach (Connection *connection, connections) {
|
||||||
|
settings.beginGroup(QString::number(i++));
|
||||||
|
settings.setValue("url", connection->url());
|
||||||
|
settings.setValue("bearerType", connection->bearerType());
|
||||||
|
settings.value("secure", connection->secure());
|
||||||
|
settings.setValue("displayName", connection->displayName());
|
||||||
|
settings.endGroup();
|
||||||
|
}
|
||||||
|
settings.endGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaDiscovery::syncCloudDevices()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_awsClient->awsDevices()->rowCount(); i++) {
|
||||||
|
AWSDevice *d = m_awsClient->awsDevices()->get(i);
|
||||||
|
NymeaHost *host = m_nymeaHosts->find(d->id());
|
||||||
|
if (!host) {
|
||||||
|
host = new NymeaHost();
|
||||||
|
host->setUuid(d->id());
|
||||||
|
host->setName(d->name());
|
||||||
|
qDebug() << "CloudDiscovery: Adding new host:" << host->name() << host->uuid().toString();
|
||||||
|
m_nymeaHosts->addHost(host);
|
||||||
|
}
|
||||||
|
QUrl url;
|
||||||
|
url.setScheme("cloud");
|
||||||
|
url.setHost(d->id());
|
||||||
|
Connection *conn = host->connections()->find(url);
|
||||||
|
if (!conn) {
|
||||||
|
conn = new Connection(url, Connection::BearerTypeCloud, true, d->id());
|
||||||
|
qDebug() << "CloudDiscovery: Adding new connection to host:" << host->name() << conn->url().toString();
|
||||||
|
host->connections()->addConnection(conn);
|
||||||
|
}
|
||||||
|
conn->setOnline(d->online());
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<NymeaHost*> hostsToRemove;
|
||||||
|
for (int i = 0; i < m_nymeaHosts->rowCount(); i++) {
|
||||||
|
NymeaHost *host = m_nymeaHosts->get(i);
|
||||||
|
for (int j = 0; j < host->connections()->rowCount(); j++) {
|
||||||
|
if (host->connections()->get(j)->bearerType() == Connection::BearerTypeCloud) {
|
||||||
|
if (m_awsClient->awsDevices()->getDevice(host->uuid().toString()) == nullptr) {
|
||||||
|
host->connections()->removeConnection(j);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (host->connections()->rowCount() == 0) {
|
||||||
|
hostsToRemove.append(host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!hostsToRemove.isEmpty()) {
|
||||||
|
m_nymeaHosts->removeHost(hostsToRemove.takeFirst());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaDiscovery::loadFromDisk()
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
settings.beginGroup("HostCache");
|
||||||
|
foreach (const QString &serverUuid, settings.childGroups()) {
|
||||||
|
settings.beginGroup(serverUuid);
|
||||||
|
NymeaHost* host = m_nymeaHosts->find(QUuid(serverUuid));
|
||||||
|
if (!host) {
|
||||||
|
host = new NymeaHost(m_nymeaHosts);
|
||||||
|
host->setName(settings.value("name").toString());
|
||||||
|
host->setUuid(QUuid(serverUuid));
|
||||||
|
m_nymeaHosts->addHost(host);
|
||||||
|
}
|
||||||
|
qDebug() << "Loaded Host from cache" << host->name() << host->uuid();
|
||||||
|
foreach (const QString &group, settings.childGroups()) {
|
||||||
|
settings.beginGroup(group);
|
||||||
|
QString url = settings.value("url").toString();
|
||||||
|
Connection* connection = host->connections()->find(url);
|
||||||
|
if (!connection) {
|
||||||
|
Connection::BearerType bearerType = static_cast<Connection::BearerType>(settings.value("bearerType").toInt());
|
||||||
|
bool secure = settings.value("secure").toBool();
|
||||||
|
QString displayName = settings.value("displayName").toString();
|
||||||
|
connection = new Connection(url, bearerType, secure, displayName, host);
|
||||||
|
host->connections()->addConnection(connection);
|
||||||
|
qDebug() << "|- Connection:" << group << connection->url() << connection->bearerType() << "secure:" << connection->secure();
|
||||||
|
}
|
||||||
|
settings.endGroup();
|
||||||
|
}
|
||||||
|
settings.endGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaDiscovery::updateActiveBearers()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
@ -3,10 +3,12 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
#include "connection/awsclient.h"
|
#include "connection/awsclient.h"
|
||||||
|
#include "connection/nymeahost.h"
|
||||||
|
|
||||||
class DiscoveryModel;
|
class NymeaHosts;
|
||||||
class UpnpDiscovery;
|
class UpnpDiscovery;
|
||||||
class ZeroconfDiscovery;
|
class ZeroconfDiscovery;
|
||||||
class BluetoothServiceDiscovery;
|
class BluetoothServiceDiscovery;
|
||||||
@ -16,39 +18,51 @@ class NymeaDiscovery : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(bool discovering READ discovering WRITE setDiscovering NOTIFY discoveringChanged)
|
Q_PROPERTY(bool discovering READ discovering WRITE setDiscovering NOTIFY discoveringChanged)
|
||||||
Q_PROPERTY(DiscoveryModel *discoveryModel READ discoveryModel CONSTANT)
|
|
||||||
|
|
||||||
Q_PROPERTY(AWSClient* awsClient READ awsClient WRITE setAwsClient NOTIFY awsClientChanged)
|
Q_PROPERTY(AWSClient* awsClient READ awsClient WRITE setAwsClient NOTIFY awsClientChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(NymeaHosts* nymeaHosts READ nymeaHosts CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit NymeaDiscovery(QObject *parent = nullptr);
|
explicit NymeaDiscovery(QObject *parent = nullptr);
|
||||||
|
~NymeaDiscovery();
|
||||||
|
|
||||||
bool discovering() const;
|
bool discovering() const;
|
||||||
void setDiscovering(bool discovering);
|
void setDiscovering(bool discovering);
|
||||||
|
|
||||||
DiscoveryModel *discoveryModel() const;
|
NymeaHosts *nymeaHosts() const;
|
||||||
|
|
||||||
AWSClient* awsClient() const;
|
AWSClient* awsClient() const;
|
||||||
void setAwsClient(AWSClient *awsClient);
|
void setAwsClient(AWSClient *awsClient);
|
||||||
|
|
||||||
|
Q_INVOKABLE void cacheHost(NymeaHost* host);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void discoveringChanged();
|
void discoveringChanged();
|
||||||
void awsClientChanged();
|
void awsClientChanged();
|
||||||
|
|
||||||
|
void serverUuidResolved(const QUuid &uuid, const QString &url);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void syncCloudDevices();
|
void syncCloudDevices();
|
||||||
|
|
||||||
|
void loadFromDisk();
|
||||||
|
|
||||||
|
void updateActiveBearers();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_discovering = false;
|
bool m_discovering = false;
|
||||||
DiscoveryModel *m_discoveryModel = nullptr;
|
NymeaHosts *m_nymeaHosts = nullptr;
|
||||||
|
|
||||||
|
AWSClient *m_awsClient = nullptr;
|
||||||
|
|
||||||
UpnpDiscovery *m_upnp = nullptr;
|
UpnpDiscovery *m_upnp = nullptr;
|
||||||
ZeroconfDiscovery *m_zeroConf = nullptr;
|
ZeroconfDiscovery *m_zeroConf = nullptr;
|
||||||
BluetoothServiceDiscovery *m_bluetooth = nullptr;
|
BluetoothServiceDiscovery *m_bluetooth = nullptr;
|
||||||
AWSClient *m_awsClient = nullptr;
|
|
||||||
|
|
||||||
QTimer m_cloudPollTimer;
|
QTimer m_cloudPollTimer;
|
||||||
|
|
||||||
|
QList<QUuid> m_pendingHostResolutions;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // NYMEADISCOVERY_H
|
#endif // NYMEADISCOVERY_H
|
||||||
@ -25,9 +25,9 @@
|
|||||||
#include <QXmlStreamReader>
|
#include <QXmlStreamReader>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
|
|
||||||
UpnpDiscovery::UpnpDiscovery(DiscoveryModel *discoveryModel, QObject *parent) :
|
UpnpDiscovery::UpnpDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
m_discoveryModel(discoveryModel)
|
m_nymeaHosts(nymeaHosts)
|
||||||
{
|
{
|
||||||
m_networkAccessManager = new QNetworkAccessManager(this);
|
m_networkAccessManager = new QNetworkAccessManager(this);
|
||||||
connect(m_networkAccessManager, &QNetworkAccessManager::finished, this, &UpnpDiscovery::networkReplyFinished);
|
connect(m_networkAccessManager, &QNetworkAccessManager::finished, this, &UpnpDiscovery::networkReplyFinished);
|
||||||
@ -240,12 +240,12 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply)
|
|||||||
|
|
||||||
// qDebug() << "discovered device" << uuid << name << discoveredAddress << version << connections << data;
|
// qDebug() << "discovered device" << uuid << name << discoveredAddress << version << connections << data;
|
||||||
|
|
||||||
DiscoveryDevice* device = m_discoveryModel->find(uuid);
|
NymeaHost* device = m_nymeaHosts->find(uuid);
|
||||||
if (!device) {
|
if (!device) {
|
||||||
device = new DiscoveryDevice(m_discoveryModel);
|
device = new NymeaHost(m_nymeaHosts);
|
||||||
device->setUuid(uuid);
|
device->setUuid(uuid);
|
||||||
qDebug() << "UPnP: Adding new host to model";
|
qDebug() << "UPnP: Adding new host to model";
|
||||||
m_discoveryModel->addDevice(device);
|
m_nymeaHosts->addHost(device);
|
||||||
}
|
}
|
||||||
device->setName(name);
|
device->setName(name);
|
||||||
device->setVersion(version);
|
device->setVersion(version);
|
||||||
@ -27,14 +27,14 @@
|
|||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "discoverydevice.h"
|
#include "../nymeahost.h"
|
||||||
#include "discoverymodel.h"
|
#include "../nymeahosts.h"
|
||||||
|
|
||||||
class UpnpDiscovery : public QObject
|
class UpnpDiscovery : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit UpnpDiscovery(DiscoveryModel *discoveryModel, QObject *parent = 0);
|
explicit UpnpDiscovery(NymeaHosts *nymeaHosts, QObject *parent = nullptr);
|
||||||
|
|
||||||
bool discovering() const;
|
bool discovering() const;
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ private:
|
|||||||
|
|
||||||
QTimer m_repeatTimer;
|
QTimer m_repeatTimer;
|
||||||
|
|
||||||
DiscoveryModel *m_discoveryModel;
|
NymeaHosts *m_nymeaHosts;
|
||||||
|
|
||||||
QHash<QNetworkReply *, QHostAddress> m_runningReplies;
|
QHash<QNetworkReply *, QHostAddress> m_runningReplies;
|
||||||
QList<QUrl> m_foundDevices;
|
QList<QUrl> m_foundDevices;
|
||||||
@ -57,7 +57,7 @@ private:
|
|||||||
signals:
|
signals:
|
||||||
void discoveringChanged();
|
void discoveringChanged();
|
||||||
void availableChanged();
|
void availableChanged();
|
||||||
void discoveryModelChanged();
|
void nymeaHostsChanged();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void writeDiscoveryPacket();
|
void writeDiscoveryPacket();
|
||||||
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
#include "discoverydevice.h"
|
#include "../nymeahost.h"
|
||||||
|
|
||||||
ZeroconfDiscovery::ZeroconfDiscovery(DiscoveryModel *discoveryModel, QObject *parent) :
|
ZeroconfDiscovery::ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
m_discoveryModel(discoveryModel)
|
m_nymeaHosts(nymeaHosts)
|
||||||
{
|
{
|
||||||
#ifdef WITH_ZEROCONF
|
#ifdef WITH_ZEROCONF
|
||||||
// NOTE: There seem to be too many issues in QtZeroConf and IPv6.
|
// NOTE: There seem to be too many issues in QtZeroConf and IPv6.
|
||||||
@ -70,6 +70,16 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Workaround a bug in deeper layers (I believe it's avahi, but could be QtZeroConf too):
|
||||||
|
// Sometimes the ip() field contains an IPv6 address. In that case the entry is likely garbage as
|
||||||
|
// it does not mean the host necessarily exports the services on IPv6.
|
||||||
|
bool isIPv4;
|
||||||
|
entry.ip().toIPv4Address(&isIPv4);
|
||||||
|
if (!isIPv4) {
|
||||||
|
qDebug() << "Skipping invalid Avahi entry: IPv4:" << entry.ip();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// qDebug() << "zeroconf service discovered" << entry.type() << entry.name() << " IP:" << entry.ip() << "IPv6:" << entry.ipv6() << entry.txt();
|
// qDebug() << "zeroconf service discovered" << entry.type() << entry.name() << " IP:" << entry.ip() << "IPv6:" << entry.ipv6() << entry.txt();
|
||||||
|
|
||||||
QString uuid;
|
QString uuid;
|
||||||
@ -91,18 +101,18 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
|
|||||||
version = txtRecord.second;
|
version = txtRecord.second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qDebug() << "avahi service entry added" << serverName << uuid << sslEnabled;
|
// qDebug() << "avahi service entry added" << serverName << uuid << sslEnabled;
|
||||||
|
|
||||||
|
|
||||||
DiscoveryDevice* device = m_discoveryModel->find(uuid);
|
NymeaHost* host = m_nymeaHosts->find(uuid);
|
||||||
if (!device) {
|
if (!host) {
|
||||||
device = new DiscoveryDevice(m_discoveryModel);
|
host = new NymeaHost(m_nymeaHosts);
|
||||||
device->setUuid(uuid);
|
host->setUuid(uuid);
|
||||||
qDebug() << "ZeroConf: Adding new host:" << serverName << uuid;
|
qDebug() << "ZeroConf: Adding new host:" << serverName << uuid;
|
||||||
m_discoveryModel->addDevice(device);
|
m_nymeaHosts->addHost(host);
|
||||||
}
|
}
|
||||||
device->setName(serverName);
|
host->setName(serverName);
|
||||||
device->setVersion(version);
|
host->setVersion(version);
|
||||||
QUrl url;
|
QUrl url;
|
||||||
// NOTE: On linux this is "_jsonrpc._tcp" while on apple systems this is "_jsonrpc._tcp."
|
// NOTE: On linux this is "_jsonrpc._tcp" while on apple systems this is "_jsonrpc._tcp."
|
||||||
if (entry.type().startsWith("_jsonrpc._tcp")) {
|
if (entry.type().startsWith("_jsonrpc._tcp")) {
|
||||||
@ -112,12 +122,16 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry)
|
|||||||
}
|
}
|
||||||
url.setHost(!entry.ip().isNull() ? entry.ip().toString() : entry.ipv6().toString());
|
url.setHost(!entry.ip().isNull() ? entry.ip().toString() : entry.ipv6().toString());
|
||||||
url.setPort(entry.port());
|
url.setPort(entry.port());
|
||||||
if (!device->connections()->find(url)){
|
Connection *connection = host->connections()->find(url);
|
||||||
qDebug() << "Zeroconf: Adding new connection to host:" << device->name() << url.toString();
|
if (!connection) {
|
||||||
|
qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString();
|
||||||
QString displayName = QString("%1:%2").arg(url.host()).arg(url.port());
|
QString displayName = QString("%1:%2").arg(url.host()).arg(url.port());
|
||||||
Connection *connection = new Connection(url, Connection::BearerTypeWifi, sslEnabled, displayName);
|
connection = new Connection(url, Connection::BearerTypeWifi, sslEnabled, displayName);
|
||||||
|
connection->setOnline(true);
|
||||||
|
host->connections()->addConnection(connection);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString();
|
||||||
connection->setOnline(true);
|
connection->setOnline(true);
|
||||||
device->connections()->addConnection(connection);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,8 +163,8 @@ void ZeroconfDiscovery::serviceEntryRemoved(const QZeroConfService &entry)
|
|||||||
|
|
||||||
// qDebug() << "Zeroconf: Service entry removed" << entry.name();
|
// qDebug() << "Zeroconf: Service entry removed" << entry.name();
|
||||||
|
|
||||||
DiscoveryDevice* device = m_discoveryModel->find(uuid);
|
NymeaHost* host = m_nymeaHosts->find(uuid);
|
||||||
if (!device) {
|
if (!host) {
|
||||||
// Nothing to do...
|
// Nothing to do...
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -163,19 +177,13 @@ void ZeroconfDiscovery::serviceEntryRemoved(const QZeroConfService &entry)
|
|||||||
}
|
}
|
||||||
url.setHost(!entry.ip().isNull() ? entry.ip().toString() : entry.ipv6().toString());
|
url.setHost(!entry.ip().isNull() ? entry.ip().toString() : entry.ipv6().toString());
|
||||||
url.setPort(entry.port());
|
url.setPort(entry.port());
|
||||||
Connection *connection = device->connections()->find(url);
|
Connection *connection = host->connections()->find(url);
|
||||||
if (!connection){
|
if (!connection){
|
||||||
// Connection url not found...
|
// Connection url not found...
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ok, now we need to remove it
|
qDebug() << "Zeroconf: Setting connection offline:" << host->name() << url.toString();
|
||||||
device->connections()->removeConnection(connection);
|
connection->setOnline(false);
|
||||||
|
|
||||||
// And if there aren't any connections left, remove the entire device
|
|
||||||
if (device->connections()->rowCount() == 0) {
|
|
||||||
qDebug() << "Zeroconf: Removing connection from host:" << device->name() << url.toString();
|
|
||||||
m_discoveryModel->removeDevice(device);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -5,7 +5,7 @@
|
|||||||
#include "qzeroconf.h"
|
#include "qzeroconf.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "discoverymodel.h"
|
#include "../nymeahosts.h"
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
@ -14,14 +14,14 @@ class ZeroconfDiscovery : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ZeroconfDiscovery(DiscoveryModel *discoveryModel, QObject *parent = nullptr);
|
explicit ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent = nullptr);
|
||||||
~ZeroconfDiscovery();
|
~ZeroconfDiscovery();
|
||||||
|
|
||||||
bool available() const;
|
bool available() const;
|
||||||
bool discovering() const;
|
bool discovering() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DiscoveryModel *m_discoveryModel;
|
NymeaHosts *m_nymeaHosts;
|
||||||
|
|
||||||
#ifdef WITH_ZEROCONF
|
#ifdef WITH_ZEROCONF
|
||||||
QZeroConf *m_zeroconfJsonRPC = nullptr;
|
QZeroConf *m_zeroconfJsonRPC = nullptr;
|
||||||
@ -1,4 +1,5 @@
|
|||||||
#include "nymeaconnection.h"
|
#include "nymeaconnection.h"
|
||||||
|
#include "nymeahost.h"
|
||||||
|
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
@ -9,61 +10,32 @@
|
|||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include "nymeatransportinterface.h"
|
#include "nymeatransportinterface.h"
|
||||||
|
|
||||||
NymeaConnection::NymeaConnection(QObject *parent) : QObject(parent)
|
NymeaConnection::NymeaConnection(QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
}
|
m_networkConfigManager = new QNetworkConfigurationManager(this);
|
||||||
|
|
||||||
bool NymeaConnection::connect(const QString &url)
|
QObject::connect(m_networkConfigManager, &QNetworkConfigurationManager::configurationAdded, this, [this](const QNetworkConfiguration &config){
|
||||||
{
|
// qDebug() << "Network configuration added:" << config.name() << config.bearerTypeName() << config.purpose();
|
||||||
if (connected()) {
|
updateActiveBearers();
|
||||||
qWarning() << "Already connected. Cannot connect multiple times";
|
});
|
||||||
return false;
|
QObject::connect(m_networkConfigManager, &QNetworkConfigurationManager::configurationRemoved, this, [this](const QNetworkConfiguration &config){
|
||||||
}
|
// qDebug() << "Network configuration removed:" << config.name() << config.bearerTypeName() << config.purpose();
|
||||||
|
updateActiveBearers();
|
||||||
|
});
|
||||||
|
|
||||||
m_currentUrl = QUrl(url);
|
updateActiveBearers();
|
||||||
emit currentUrlChanged();
|
|
||||||
if (!m_transports.contains(m_currentUrl.scheme())) {
|
|
||||||
qWarning() << "Cannot connect to urls of scheme" << m_currentUrl.scheme() << "Supported schemes are" << m_transports.keys();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new transport
|
|
||||||
m_currentTransport = m_transports.value(m_currentUrl.scheme())->createTransport();
|
|
||||||
QObject::connect(m_currentTransport, &NymeaTransportInterface::sslErrors, this, &NymeaConnection::onSslErrors);
|
|
||||||
QObject::connect(m_currentTransport, &NymeaTransportInterface::error, this, &NymeaConnection::onError);
|
|
||||||
QObject::connect(m_currentTransport, &NymeaTransportInterface::connected, this, &NymeaConnection::onConnected);
|
|
||||||
QObject::connect(m_currentTransport, &NymeaTransportInterface::disconnected, this, &NymeaConnection::onDisconnected);
|
|
||||||
QObject::connect(m_currentTransport, &NymeaTransportInterface::dataReady, this, &NymeaConnection::dataAvailable);
|
|
||||||
|
|
||||||
// Load any certificate we might have for this url
|
|
||||||
QByteArray pem;
|
|
||||||
if (loadPem(m_currentUrl, pem)) {
|
|
||||||
qDebug() << "Loaded SSL certificate for" << m_currentUrl.host();
|
|
||||||
QList<QSslError> expectedSslErrors;
|
|
||||||
expectedSslErrors.append(QSslError::HostNameMismatch);
|
|
||||||
expectedSslErrors.append(QSslError(QSslError::SelfSignedCertificate, QSslCertificate(pem)));
|
|
||||||
m_currentTransport->ignoreSslErrors(expectedSslErrors);
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "Connecting to:" << m_currentUrl;
|
|
||||||
return m_currentTransport->connect(m_currentUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NymeaConnection::disconnect()
|
|
||||||
{
|
|
||||||
if (!m_currentTransport || m_currentTransport->connectionState() == NymeaTransportInterface::ConnectionStateDisconnected) {
|
|
||||||
qWarning() << "not connected, cannot disconnect";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_currentTransport->disconnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NymeaConnection::acceptCertificate(const QString &url, const QByteArray &pem)
|
void NymeaConnection::acceptCertificate(const QString &url, const QByteArray &pem)
|
||||||
{
|
{
|
||||||
storePem(url, pem);
|
storePem(url, pem);
|
||||||
|
if (m_currentHost) {
|
||||||
|
connectInternal(m_currentHost);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NymeaConnection::isTrusted(const QString &url)
|
bool NymeaConnection::isTrusted(const QString &url)
|
||||||
@ -84,30 +56,64 @@ bool NymeaConnection::isTrusted(const QString &url)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connection::BearerTypes NymeaConnection::availableBearerTypes() const
|
||||||
|
{
|
||||||
|
return m_availableBearerTypes;
|
||||||
|
}
|
||||||
|
|
||||||
bool NymeaConnection::connected()
|
bool NymeaConnection::connected()
|
||||||
{
|
{
|
||||||
return m_currentTransport && m_currentTransport->connectionState() == NymeaTransportInterface::ConnectionStateConnected;
|
return m_currentHost && m_currentTransport && m_currentTransport->connectionState() == NymeaTransportInterface::ConnectionStateConnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NymeaConnection::url() const
|
NymeaConnection::ConnectionStatus NymeaConnection::connectionStatus() const
|
||||||
{
|
{
|
||||||
return m_currentUrl.toString();
|
return m_connectionStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NymeaConnection::hostAddress() const
|
NymeaHost *NymeaConnection::currentHost() const
|
||||||
{
|
{
|
||||||
return m_currentUrl.host();
|
return m_currentHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
int NymeaConnection::port() const
|
void NymeaConnection::setCurrentHost(NymeaHost *host)
|
||||||
{
|
{
|
||||||
return m_currentUrl.port();
|
if (m_currentHost == host) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_currentTransport) {
|
||||||
|
m_currentTransport = nullptr;
|
||||||
|
emit currentConnectionChanged();
|
||||||
|
emit connectedChanged(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!m_transportCandidates.isEmpty()) {
|
||||||
|
NymeaTransportInterface *transport = m_transportCandidates.keys().first();
|
||||||
|
m_transportCandidates.remove(transport);
|
||||||
|
transport->deleteLater();
|
||||||
|
}
|
||||||
|
if (m_currentHost) {
|
||||||
|
m_currentHost = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_currentHost = host;
|
||||||
|
emit currentHostChanged();
|
||||||
|
|
||||||
|
m_connectionStatus = ConnectionStatusConnecting;
|
||||||
|
emit connectionStatusChanged();
|
||||||
|
|
||||||
|
if (m_currentHost) {
|
||||||
|
connectInternal(m_currentHost);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NymeaConnection::bluetoothAddress() const
|
Connection *NymeaConnection::currentConnection() const
|
||||||
{
|
{
|
||||||
QUrlQuery query(m_currentUrl);
|
if (!m_currentHost || !m_currentTransport) {
|
||||||
return query.queryItemValue("mac");
|
return nullptr;
|
||||||
|
}
|
||||||
|
return m_transportCandidates.value(m_currentTransport);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NymeaConnection::sendData(const QByteArray &data)
|
void NymeaConnection::sendData(const QByteArray &data)
|
||||||
@ -122,15 +128,16 @@ void NymeaConnection::sendData(const QByteArray &data)
|
|||||||
|
|
||||||
void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
|
void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
|
||||||
{
|
{
|
||||||
qDebug() << "Connection: SSL errors:" << errors;
|
NymeaTransportInterface *transport = qobject_cast<NymeaTransportInterface*>(sender());
|
||||||
|
|
||||||
|
qDebug() << "SSL errors for url:" << transport->url();
|
||||||
QList<QSslError> ignoredErrors;
|
QList<QSslError> ignoredErrors;
|
||||||
foreach (const QSslError &error, errors) {
|
foreach (const QSslError &error, errors) {
|
||||||
|
qDebug() << error.errorString();
|
||||||
if (error.error() == QSslError::HostNameMismatch) {
|
if (error.error() == QSslError::HostNameMismatch) {
|
||||||
qDebug() << "Ignoring host mismatch on certificate.";
|
qDebug() << "Ignoring host mismatch on certificate.";
|
||||||
ignoredErrors.append(error);
|
ignoredErrors.append(error);
|
||||||
} else if (error.error() == QSslError::SelfSignedCertificate || error.error() == QSslError::CertificateUntrusted) {
|
} else if (error.error() == QSslError::SelfSignedCertificate || error.error() == QSslError::CertificateUntrusted) {
|
||||||
qDebug() << "have a self signed certificate." << error.certificate();
|
|
||||||
|
|
||||||
// Check our cert DB
|
// Check our cert DB
|
||||||
QByteArray pem;
|
QByteArray pem;
|
||||||
|
|
||||||
@ -140,7 +147,7 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
|
|||||||
// However, we want to emit verifyConnectionCertificate in any case here.
|
// However, we want to emit verifyConnectionCertificate in any case here.
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
settings.beginGroup("acceptedCertificates");
|
settings.beginGroup("acceptedCertificates");
|
||||||
QByteArray storedFingerPrint = settings.value(m_currentUrl.host()).toByteArray();
|
QByteArray storedFingerPrint = settings.value(transport->url().host()).toByteArray();
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
QByteArray certificateFingerprint;
|
QByteArray certificateFingerprint;
|
||||||
@ -158,15 +165,18 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
|
|||||||
ignoredErrors.append(error);
|
ignoredErrors.append(error);
|
||||||
|
|
||||||
// Update the config to use the new system:
|
// Update the config to use the new system:
|
||||||
storePem(m_currentUrl, error.certificate().toPem());
|
storePem(transport->url(), error.certificate().toPem());
|
||||||
|
|
||||||
// Check new style PEM storage
|
// Check new style PEM storage
|
||||||
} else if (loadPem(m_currentUrl, pem) && pem == error.certificate().toPem()) {
|
} else if (loadPem(transport->url(), pem) && pem == error.certificate().toPem()) {
|
||||||
qDebug() << "Found a SSL certificate for this host. Ignoring error.";
|
qDebug() << "Found a SSL certificate for this host. Ignoring error.";
|
||||||
ignoredErrors.append(error);
|
ignoredErrors.append(error);
|
||||||
|
|
||||||
// Ok... nothing found... Pop up the message
|
// Ok... nothing found... Pop up the message
|
||||||
} else {
|
} else {
|
||||||
|
qDebug() << "Host presents an unknown self signed certificate:" << error.certificate();
|
||||||
|
qDebug() << "Asking user for confirmation.";
|
||||||
|
|
||||||
QStringList info;
|
QStringList info;
|
||||||
info << tr("Common Name:") << error.certificate().issuerInfo(QSslCertificate::CommonName);
|
info << tr("Common Name:") << error.certificate().issuerInfo(QSslCertificate::CommonName);
|
||||||
info << tr("Oragnisation:") <<error.certificate().issuerInfo(QSslCertificate::Organization);
|
info << tr("Oragnisation:") <<error.certificate().issuerInfo(QSslCertificate::Organization);
|
||||||
@ -177,7 +187,9 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
|
|||||||
// info << tr("Name Qualifier:")<< error.certificate().issuerInfo(QSslCertificate::DistinguishedNameQualifier);
|
// info << tr("Name Qualifier:")<< error.certificate().issuerInfo(QSslCertificate::DistinguishedNameQualifier);
|
||||||
// info << tr("Email:")<< error.certificate().issuerInfo(QSslCertificate::EmailAddress);
|
// info << tr("Email:")<< error.certificate().issuerInfo(QSslCertificate::EmailAddress);
|
||||||
|
|
||||||
emit verifyConnectionCertificate(m_currentUrl.toString(), info, certificateFingerprint, error.certificate().toPem());
|
m_connectionStatus = ConnectionStatusSslUntrusted;
|
||||||
|
emit connectionStatusChanged();
|
||||||
|
emit verifyConnectionCertificate(transport->url().toString(), info, certificateFingerprint, error.certificate().toPem());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Reject the connection on all other errors...
|
// Reject the connection on all other errors...
|
||||||
@ -187,38 +199,227 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
|
|||||||
if (ignoredErrors == errors) {
|
if (ignoredErrors == errors) {
|
||||||
// Note, due to a workaround in the WebSocketTransport we must not call this
|
// Note, due to a workaround in the WebSocketTransport we must not call this
|
||||||
// unless we've handled all the errors or the websocket will ignore unhandled errors too...
|
// unless we've handled all the errors or the websocket will ignore unhandled errors too...
|
||||||
m_currentTransport->ignoreSslErrors(ignoredErrors);
|
transport->ignoreSslErrors(ignoredErrors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NymeaConnection::onError(QAbstractSocket::SocketError error)
|
void NymeaConnection::onError(QAbstractSocket::SocketError error)
|
||||||
{
|
{
|
||||||
QMetaEnum errorEnum = QMetaEnum::fromType<QAbstractSocket::SocketError>();
|
QMetaEnum errorEnum = QMetaEnum::fromType<QAbstractSocket::SocketError>();
|
||||||
emit connectionError(errorEnum.valueToKey(error));
|
QString errorString = errorEnum.valueToKey(error);
|
||||||
|
|
||||||
|
NymeaTransportInterface* transport = qobject_cast<NymeaTransportInterface*>(sender());
|
||||||
|
|
||||||
|
ConnectionStatus errorStatus = ConnectionStatusUnknownError;
|
||||||
|
switch (error) {
|
||||||
|
case QAbstractSocket::ConnectionRefusedError:
|
||||||
|
errorStatus = ConnectionStatusConnectionRefused;
|
||||||
|
break;
|
||||||
|
case QAbstractSocket::HostNotFoundError:
|
||||||
|
errorStatus = ConnectionStatusHostNotFound;
|
||||||
|
break;
|
||||||
|
case QAbstractSocket::NetworkError:
|
||||||
|
errorStatus = ConnectionStatusBearerFailed;
|
||||||
|
break;
|
||||||
|
case QAbstractSocket::RemoteHostClosedError:
|
||||||
|
errorStatus = ConnectionStatusRemoteHostClosed;
|
||||||
|
break;
|
||||||
|
case QAbstractSocket::SocketTimeoutError:
|
||||||
|
errorStatus = ConnectionStatusTimeout;
|
||||||
|
break;
|
||||||
|
case QAbstractSocket::SslInternalError:
|
||||||
|
case QAbstractSocket::SslInvalidUserDataError:
|
||||||
|
errorStatus = ConnectionStatusSslError;
|
||||||
|
break;
|
||||||
|
case QAbstractSocket::SslHandshakeFailedError:
|
||||||
|
errorStatus = ConnectionStatusSslUntrusted;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorStatus = ConnectionStatusUnknownError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transport == m_currentTransport) {
|
||||||
|
qDebug() << "Current transport failed:" << error;
|
||||||
|
// The current transport failed, forward the error
|
||||||
|
m_connectionStatus = errorStatus;
|
||||||
|
emit connectionStatusChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_currentTransport) {
|
||||||
|
// We're trying to connect and one of the transports failed...
|
||||||
|
if (m_transportCandidates.contains(transport)) {
|
||||||
|
m_transportCandidates.remove(transport);
|
||||||
|
transport->deleteLater();
|
||||||
|
}
|
||||||
|
qDebug() << "A transport error happened for" << transport->url() << error << "(Still trying on" << m_transportCandidates.count() << "connections)";
|
||||||
|
if (m_transportCandidates.isEmpty()) {
|
||||||
|
m_connectionStatus = errorStatus;
|
||||||
|
emit connectionStatusChanged();
|
||||||
|
|
||||||
|
if (m_connectionStatus != ConnectionStatusSslUntrusted) {
|
||||||
|
QTimer::singleShot(1000, m_currentHost, [this](){
|
||||||
|
connectInternal(m_currentHost);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NymeaConnection::onConnected()
|
void NymeaConnection::onConnected()
|
||||||
{
|
{
|
||||||
if (m_currentTransport != sender()) {
|
NymeaTransportInterface* newTransport = qobject_cast<NymeaTransportInterface*>(sender());
|
||||||
qWarning() << "NymeaConnection: An inactive transport is emitting signals... ignoring.";
|
if (!m_currentTransport) {
|
||||||
|
m_currentTransport = newTransport;
|
||||||
|
qDebug() << "NymeaConnection: Connected to" << m_currentHost->name() << "via" << m_currentTransport->url();
|
||||||
|
emit connectedChanged(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_currentTransport != newTransport) {
|
||||||
|
qDebug() << "Alternative connection established:" << newTransport->url();
|
||||||
|
|
||||||
|
// In theory, we could roam from one connection to another.
|
||||||
|
// However, in practice it turns out there are too many issues for this to be reliable
|
||||||
|
// So lets just tear down any alternative connection that comes up again.
|
||||||
|
|
||||||
|
qDebug() << "Dropping alternative connection again...";
|
||||||
|
m_transportCandidates.remove(newTransport);
|
||||||
|
newTransport->deleteLater();
|
||||||
|
|
||||||
|
|
||||||
|
// Connection *existingConnection = m_transportCandidates.value(m_currentTransport);
|
||||||
|
// Connection *alternativeConnection = m_transportCandidates.value(newTransport);
|
||||||
|
// if (alternativeConnection->priority() > existingConnection->priority()) {
|
||||||
|
// qDebug() << "New connection has higher priority! Roaming from" << existingConnection->url() << existingConnection->priority() << "to" << alternativeConnection->url() << alternativeConnection->priority();
|
||||||
|
// m_transportCandidates.remove(m_currentTransport);
|
||||||
|
// m_currentTransport->deleteLater();
|
||||||
|
// m_currentTransport = newTransport;
|
||||||
|
// } else {
|
||||||
|
// qDebug() << "Connection" << alternativeConnection->url() << alternativeConnection->priority() << "has lower priority than existing" << existingConnection->url() << existingConnection->priority();
|
||||||
|
// m_transportCandidates.remove(newTransport);
|
||||||
|
// newTransport->deleteLater();
|
||||||
|
// }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
qDebug() << "NymeaConnection: connected.";
|
|
||||||
emit connectedChanged(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NymeaConnection::onDisconnected()
|
void NymeaConnection::onDisconnected()
|
||||||
{
|
{
|
||||||
if (m_currentTransport != sender()) {
|
NymeaTransportInterface* t = qobject_cast<NymeaTransportInterface*>(sender());
|
||||||
qWarning() << "NymeaConnection: An inactive transport is emitting signals... ignoring.";
|
if (m_currentTransport != t) {
|
||||||
|
qWarning() << "NymeaConnection: An inactive transport for url" << t->url() << "disconnected... Cleaning up...";
|
||||||
|
if (m_transportCandidates.contains(t)) {
|
||||||
|
m_transportCandidates.remove(t);
|
||||||
|
}
|
||||||
|
t->deleteLater();
|
||||||
|
|
||||||
|
if (!m_currentTransport && m_transportCandidates.isEmpty()) {
|
||||||
|
qDebug() << "Last connection dropped. Trying to reconnect..";
|
||||||
|
QTimer::singleShot(1000, this, [this](){
|
||||||
|
if (m_currentHost) {
|
||||||
|
connectInternal(m_currentHost);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_transportCandidates.remove(m_currentTransport);
|
||||||
m_currentTransport->deleteLater();
|
m_currentTransport->deleteLater();
|
||||||
m_currentTransport = nullptr;
|
m_currentTransport = nullptr;
|
||||||
qDebug() << "NymeaConnection: disconnected.";
|
|
||||||
emit connectedChanged(false);
|
foreach (NymeaTransportInterface *candidate, m_transportCandidates.keys()) {
|
||||||
|
if (candidate->connectionState() == NymeaTransportInterface::ConnectionStateConnected) {
|
||||||
|
qDebug() << "Alternative connection is still up. Roaming to:" << candidate->url();
|
||||||
|
m_currentTransport = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit currentConnectionChanged();
|
||||||
|
|
||||||
|
if (!m_currentTransport) {
|
||||||
|
qDebug() << "NymeaConnection: disconnected.";
|
||||||
|
emit connectedChanged(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to reconnect, only if we're not waiting for SSL certs to be trusted.
|
||||||
|
if (m_connectionStatus != ConnectionStatusSslUntrusted) {
|
||||||
|
connectInternal(m_currentHost);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NymeaConnection::updateActiveBearers()
|
||||||
|
{
|
||||||
|
Connection::BearerTypes availableBearerTypes;
|
||||||
|
QList<QNetworkConfiguration> configs = m_networkConfigManager->allConfigurations(QNetworkConfiguration::Active);
|
||||||
|
// qDebug() << "Network configuations:" << configs.count();
|
||||||
|
foreach (const QNetworkConfiguration &config, configs) {
|
||||||
|
qDebug() << "Candidate network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName();
|
||||||
|
|
||||||
|
// NOTE: iOS doesn't correctly report bearer types. It'll be Unknown all the time
|
||||||
|
// availableBearerTypes.setFlag(Connection::BearerTypeUnknown);
|
||||||
|
|
||||||
|
availableBearerTypes.setFlag(qBearerTypeToNymeaBearerType(config.bearerType()));
|
||||||
|
}
|
||||||
|
// qDebug() << "Available bearers:" << availableBearerTypes;
|
||||||
|
if (m_availableBearerTypes != availableBearerTypes) {
|
||||||
|
|
||||||
|
qDebug() << "Available Bearer Types changed:" << availableBearerTypes;
|
||||||
|
m_availableBearerTypes = availableBearerTypes;
|
||||||
|
emit availableBearerTypesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_currentHost) {
|
||||||
|
// No host set... Nothing to do...
|
||||||
|
qDebug() << "No current host... Nothing to do...";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In theory we could try to connect via any different/new bearers now. However, in practice
|
||||||
|
// I have observed the following issues:
|
||||||
|
// - When roaming from WiFi to mobile data, we've already lost WiFi at this point
|
||||||
|
// (Unless aggressive WiFi to mobile handover is enabled on the phone)
|
||||||
|
// - When roaming from mobile to Wifi, for some reason, any new connection attempts
|
||||||
|
// fail as long as the mobile data isn't shut down by the OS.
|
||||||
|
// Those issues prevent roaming from working properly, so let's just not do anything at
|
||||||
|
// this point if there already is a connected channel, try reconnecting otherwise.
|
||||||
|
|
||||||
|
if (!m_currentTransport) {
|
||||||
|
// There's a host but no connection. Try connecting now...
|
||||||
|
qDebug() << "There's a host but no connection. Trying to connect now...";
|
||||||
|
connectInternal(m_currentHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Connection::BearerType NymeaConnection::qBearerTypeToNymeaBearerType(QNetworkConfiguration::BearerType type) const
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case QNetworkConfiguration::BearerWLAN:
|
||||||
|
return Connection::BearerTypeWifi;
|
||||||
|
case QNetworkConfiguration::BearerEthernet:
|
||||||
|
return Connection::BearerTypeEthernet;
|
||||||
|
case QNetworkConfiguration::Bearer2G:
|
||||||
|
case QNetworkConfiguration::BearerCDMA2000:
|
||||||
|
case QNetworkConfiguration::BearerWCDMA:
|
||||||
|
case QNetworkConfiguration::BearerHSPA:
|
||||||
|
case QNetworkConfiguration::BearerWiMAX:
|
||||||
|
case QNetworkConfiguration::BearerEVDO:
|
||||||
|
case QNetworkConfiguration::BearerLTE:
|
||||||
|
case QNetworkConfiguration::Bearer3G:
|
||||||
|
case QNetworkConfiguration::Bearer4G:
|
||||||
|
return Connection::BearerTypeCloud;
|
||||||
|
case QNetworkConfiguration::BearerBluetooth:
|
||||||
|
return Connection::BearerTypeBluetooth;
|
||||||
|
case QNetworkConfiguration::BearerUnknown:
|
||||||
|
return Connection::BearerTypeUnknown;
|
||||||
|
}
|
||||||
|
return Connection::BearerTypeNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool NymeaConnection::storePem(const QUrl &host, const QByteArray &pem)
|
bool NymeaConnection::storePem(const QUrl &host, const QByteArray &pem)
|
||||||
{
|
{
|
||||||
QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
|
QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
|
||||||
@ -237,6 +438,7 @@ bool NymeaConnection::storePem(const QUrl &host, const QByteArray &pem)
|
|||||||
bool NymeaConnection::loadPem(const QUrl &host, QByteArray &pem)
|
bool NymeaConnection::loadPem(const QUrl &host, QByteArray &pem)
|
||||||
{
|
{
|
||||||
QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
|
QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/");
|
||||||
|
// qDebug() << "Loading certificates from:" << dir.absoluteFilePath(host.host() + ".pem");
|
||||||
QFile certFile(dir.absoluteFilePath(host.host() + ".pem"));
|
QFile certFile(dir.absoluteFilePath(host.host() + ".pem"));
|
||||||
if (!certFile.open(QFile::ReadOnly)) {
|
if (!certFile.open(QFile::ReadOnly)) {
|
||||||
return false;
|
return false;
|
||||||
@ -249,6 +451,101 @@ bool NymeaConnection::loadPem(const QUrl &host, QByteArray &pem)
|
|||||||
void NymeaConnection::registerTransport(NymeaTransportInterfaceFactory *transportFactory)
|
void NymeaConnection::registerTransport(NymeaTransportInterfaceFactory *transportFactory)
|
||||||
{
|
{
|
||||||
foreach (const QString &scheme, transportFactory->supportedSchemes()) {
|
foreach (const QString &scheme, transportFactory->supportedSchemes()) {
|
||||||
m_transports[scheme] = transportFactory;
|
m_transportFactories[scheme] = transportFactory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NymeaConnection::connect(NymeaHost *nymeaHost, Connection *connection)
|
||||||
|
{
|
||||||
|
if (!nymeaHost) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_preferredConnection = nullptr;
|
||||||
|
if (connection) {
|
||||||
|
if (nymeaHost->connections()->find(connection->url())) {
|
||||||
|
qDebug() << "Setting preferred connection to" << connection->url();
|
||||||
|
m_preferredConnection = connection;
|
||||||
|
} else {
|
||||||
|
qWarning() << "Connection" << connection << "is not a candidate for" << nymeaHost->name() << "Not setting preferred connection.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentHost(nymeaHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaConnection::connectInternal(NymeaHost *host)
|
||||||
|
{
|
||||||
|
if (m_availableBearerTypes == Connection::BearerTypeNone) {
|
||||||
|
qDebug() << "No available bearer. Not connecting... (" << m_availableBearerTypes << ")";
|
||||||
|
m_connectionStatus = ConnectionStatusNoBearerAvailable;
|
||||||
|
emit connectionStatusChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_preferredConnection) {
|
||||||
|
qDebug() << "Preferred connection is set. Using" << m_preferredConnection->url();
|
||||||
|
connectInternal(m_preferredConnection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_availableBearerTypes.testFlag(Connection::BearerTypeWifi) || m_availableBearerTypes.testFlag(Connection::BearerTypeEthernet)) {
|
||||||
|
Connection* lanConnection = host->connections()->bestMatch(Connection::BearerTypeWifi | Connection::BearerTypeEthernet);
|
||||||
|
if (lanConnection) {
|
||||||
|
qDebug() << "Best candidate LAN connection:" << lanConnection->url();
|
||||||
|
connectInternal(lanConnection);
|
||||||
|
} else {
|
||||||
|
qDebug() << "No available LAN connection to" << host->name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_availableBearerTypes.testFlag(Connection::BearerTypeCloud)) {
|
||||||
|
Connection* wanConnection = host->connections()->bestMatch(Connection::BearerTypeCloud);
|
||||||
|
if (wanConnection) {
|
||||||
|
qDebug() << "Best candidate WAN connection:" << wanConnection->url();
|
||||||
|
connectInternal(wanConnection);
|
||||||
|
} else {
|
||||||
|
qDebug() << "No available WAN connection to" << host->name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NymeaConnection::connectInternal(Connection *connection)
|
||||||
|
{
|
||||||
|
if (!m_transportFactories.contains(connection->url().scheme())) {
|
||||||
|
qWarning() << "Cannot connect to urls of scheme" << connection->url().scheme() << "Supported schemes are" << m_transportFactories.keys();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_transportCandidates.values().contains(connection)) {
|
||||||
|
qDebug() << "Already have a connection (or connection attempt) for" << connection->url();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new transport
|
||||||
|
NymeaTransportInterface* newTransport = m_transportFactories.value(connection->url().scheme())->createTransport();
|
||||||
|
QObject::connect(newTransport, &NymeaTransportInterface::sslErrors, this, &NymeaConnection::onSslErrors);
|
||||||
|
QObject::connect(newTransport, &NymeaTransportInterface::error, this, &NymeaConnection::onError);
|
||||||
|
QObject::connect(newTransport, &NymeaTransportInterface::connected, this, &NymeaConnection::onConnected);
|
||||||
|
QObject::connect(newTransport, &NymeaTransportInterface::disconnected, this, &NymeaConnection::onDisconnected);
|
||||||
|
QObject::connect(newTransport, &NymeaTransportInterface::dataReady, this, &NymeaConnection::dataAvailable);
|
||||||
|
|
||||||
|
// Load any certificate we might have for this url
|
||||||
|
QByteArray pem;
|
||||||
|
if (loadPem(connection->url(), pem)) {
|
||||||
|
qDebug() << "Loaded SSL certificate for" << connection->url().host();
|
||||||
|
QList<QSslError> expectedSslErrors;
|
||||||
|
expectedSslErrors.append(QSslError::HostNameMismatch);
|
||||||
|
expectedSslErrors.append(QSslError(QSslError::SelfSignedCertificate, QSslCertificate(pem)));
|
||||||
|
newTransport->ignoreSslErrors(expectedSslErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_transportCandidates.insert(newTransport, connection);
|
||||||
|
qDebug() << "Connecting to:" << connection->url();
|
||||||
|
return newTransport->connect(connection->url());
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaConnection::disconnect()
|
||||||
|
{
|
||||||
|
setCurrentHost(nullptr);
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,10 @@
|
|||||||
#include <QSslError>
|
#include <QSslError>
|
||||||
#include <QAbstractSocket>
|
#include <QAbstractSocket>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QNetworkConfigurationManager>
|
||||||
|
|
||||||
|
|
||||||
|
#include "nymeahost.h"
|
||||||
|
|
||||||
class NymeaTransportInterface;
|
class NymeaTransportInterface;
|
||||||
class NymeaTransportInterfaceFactory;
|
class NymeaTransportInterfaceFactory;
|
||||||
@ -14,35 +18,56 @@ class NymeaConnection : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
|
Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
|
||||||
Q_PROPERTY(QString url READ url NOTIFY currentUrlChanged)
|
Q_PROPERTY(NymeaHost* currentHost READ currentHost WRITE setCurrentHost NOTIFY currentHostChanged)
|
||||||
Q_PROPERTY(QString hostAddress READ hostAddress NOTIFY currentUrlChanged)
|
Q_PROPERTY(Connection* currentConnection READ currentConnection NOTIFY currentConnectionChanged)
|
||||||
Q_PROPERTY(int port READ port NOTIFY currentUrlChanged)
|
Q_PROPERTY(Connection::BearerTypes availableBearerTypes READ availableBearerTypes NOTIFY availableBearerTypesChanged)
|
||||||
Q_PROPERTY(QString bluetoothAddress READ bluetoothAddress NOTIFY currentUrlChanged)
|
Q_PROPERTY(ConnectionStatus connectionStatus READ connectionStatus NOTIFY connectionStatusChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
enum ConnectionStatus {
|
||||||
|
ConnectionStatusUnconnected,
|
||||||
|
ConnectionStatusConnecting,
|
||||||
|
ConnectionStatusNoBearerAvailable,
|
||||||
|
ConnectionStatusBearerFailed,
|
||||||
|
ConnectionStatusHostNotFound,
|
||||||
|
ConnectionStatusConnectionRefused,
|
||||||
|
ConnectionStatusRemoteHostClosed,
|
||||||
|
ConnectionStatusTimeout,
|
||||||
|
ConnectionStatusSslError,
|
||||||
|
ConnectionStatusSslUntrusted,
|
||||||
|
ConnectionStatusUnknownError,
|
||||||
|
ConnectionStatusConnected
|
||||||
|
};
|
||||||
|
Q_ENUM(ConnectionStatus)
|
||||||
explicit NymeaConnection(QObject *parent = nullptr);
|
explicit NymeaConnection(QObject *parent = nullptr);
|
||||||
|
|
||||||
void registerTransport(NymeaTransportInterfaceFactory *transportFactory);
|
void registerTransport(NymeaTransportInterfaceFactory *transportFactory);
|
||||||
|
|
||||||
Q_INVOKABLE bool connect(const QString &url);
|
Q_INVOKABLE void connect(NymeaHost* nymeaHost, Connection *connection = nullptr);
|
||||||
Q_INVOKABLE void disconnect();
|
Q_INVOKABLE void disconnect();
|
||||||
Q_INVOKABLE void acceptCertificate(const QString &url, const QByteArray &pem);
|
Q_INVOKABLE void acceptCertificate(const QString &url, const QByteArray &pem);
|
||||||
Q_INVOKABLE bool isTrusted(const QString &url);
|
Q_INVOKABLE bool isTrusted(const QString &url);
|
||||||
|
|
||||||
bool connected();
|
Connection::BearerTypes availableBearerTypes() const;
|
||||||
|
|
||||||
|
bool connected();
|
||||||
|
ConnectionStatus connectionStatus() const;
|
||||||
|
|
||||||
|
NymeaHost* currentHost() const;
|
||||||
|
void setCurrentHost(NymeaHost *host);
|
||||||
|
|
||||||
|
Connection* currentConnection() const;
|
||||||
|
|
||||||
QString url() const;
|
|
||||||
QString hostAddress() const;
|
|
||||||
int port() const;
|
|
||||||
QString bluetoothAddress() const;
|
|
||||||
|
|
||||||
void sendData(const QByteArray &data);
|
void sendData(const QByteArray &data);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void currentUrlChanged();
|
void availableBearerTypesChanged();
|
||||||
void verifyConnectionCertificate(const QString &url, const QStringList &issuerInfo, const QByteArray &fingerprint, const QByteArray &pem);
|
void verifyConnectionCertificate(const QString &url, const QStringList &issuerInfo, const QByteArray &fingerprint, const QByteArray &pem);
|
||||||
|
void currentHostChanged();
|
||||||
void connectedChanged(bool connected);
|
void connectedChanged(bool connected);
|
||||||
void connectionError(const QString &error);
|
void connectionStatusChanged();
|
||||||
|
void currentConnectionChanged();
|
||||||
void dataAvailable(const QByteArray &data);
|
void dataAvailable(const QByteArray &data);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
@ -51,14 +76,26 @@ private slots:
|
|||||||
void onConnected();
|
void onConnected();
|
||||||
void onDisconnected();
|
void onDisconnected();
|
||||||
|
|
||||||
|
void updateActiveBearers();
|
||||||
private:
|
private:
|
||||||
bool storePem(const QUrl &host, const QByteArray &pem);
|
bool storePem(const QUrl &host, const QByteArray &pem);
|
||||||
bool loadPem(const QUrl &host, QByteArray &pem);
|
bool loadPem(const QUrl &host, QByteArray &pem);
|
||||||
|
|
||||||
|
void connectInternal(NymeaHost *host);
|
||||||
|
bool connectInternal(Connection *connection);
|
||||||
|
|
||||||
|
Connection::BearerType qBearerTypeToNymeaBearerType(QNetworkConfiguration::BearerType type) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QHash<QString, NymeaTransportInterfaceFactory*> m_transports;
|
ConnectionStatus m_connectionStatus = ConnectionStatusUnconnected;
|
||||||
|
QNetworkConfigurationManager *m_networkConfigManager = nullptr;
|
||||||
|
Connection::BearerTypes m_availableBearerTypes = Connection::BearerTypeNone;
|
||||||
|
|
||||||
|
QHash<QString, NymeaTransportInterfaceFactory*> m_transportFactories;
|
||||||
|
QHash<NymeaTransportInterface*, Connection*> m_transportCandidates;
|
||||||
NymeaTransportInterface *m_currentTransport = nullptr;
|
NymeaTransportInterface *m_currentTransport = nullptr;
|
||||||
QUrl m_currentUrl;
|
NymeaHost *m_currentHost = nullptr;
|
||||||
|
Connection *m_preferredConnection = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // NYMEACONNECTION_H
|
#endif // NYMEACONNECTION_H
|
||||||
|
|||||||
@ -18,32 +18,41 @@
|
|||||||
* *
|
* *
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
#include "discoverydevice.h"
|
#include "nymeahost.h"
|
||||||
|
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
DiscoveryDevice::DiscoveryDevice(QObject *parent):
|
NymeaHost::NymeaHost(QObject *parent):
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
m_connections(new Connections(this))
|
m_connections(new Connections(this))
|
||||||
{
|
{
|
||||||
|
connect(m_connections, &Connections::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, const QVector<int>){
|
||||||
|
emit connectionChanged();
|
||||||
|
});
|
||||||
|
connect(m_connections, &Connections::connectionAdded, this, [this](Connection*){
|
||||||
|
emit connectionChanged();
|
||||||
|
});
|
||||||
|
connect(m_connections, &Connections::connectionRemoved, this, [this](Connection*){
|
||||||
|
emit connectionChanged();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QUuid DiscoveryDevice::uuid() const
|
QUuid NymeaHost::uuid() const
|
||||||
{
|
{
|
||||||
return m_uuid;
|
return m_uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiscoveryDevice::setUuid(const QUuid &uuid)
|
void NymeaHost::setUuid(const QUuid &uuid)
|
||||||
{
|
{
|
||||||
m_uuid = uuid;
|
m_uuid = uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DiscoveryDevice::name() const
|
QString NymeaHost::name() const
|
||||||
{
|
{
|
||||||
return m_name;
|
return m_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiscoveryDevice::setName(const QString &name)
|
void NymeaHost::setName(const QString &name)
|
||||||
{
|
{
|
||||||
if (m_name != name) {
|
if (m_name != name) {
|
||||||
m_name = name;
|
m_name = name;
|
||||||
@ -51,12 +60,12 @@ void DiscoveryDevice::setName(const QString &name)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DiscoveryDevice::version() const
|
QString NymeaHost::version() const
|
||||||
{
|
{
|
||||||
return m_version;
|
return m_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiscoveryDevice::setVersion(const QString &version)
|
void NymeaHost::setVersion(const QString &version)
|
||||||
{
|
{
|
||||||
if (m_version != version) {
|
if (m_version != version) {
|
||||||
m_version = version;
|
m_version = version;
|
||||||
@ -64,7 +73,7 @@ void DiscoveryDevice::setVersion(const QString &version)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections* DiscoveryDevice::connections() const
|
Connections* NymeaHost::connections() const
|
||||||
{
|
{
|
||||||
return m_connections;
|
return m_connections;
|
||||||
}
|
}
|
||||||
@ -121,6 +130,7 @@ void Connections::addConnection(Connection *connection)
|
|||||||
emit dataChanged(index(idx), index(idx), {RoleOnline});
|
emit dataChanged(index(idx), index(idx), {RoleOnline});
|
||||||
});
|
});
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
|
emit connectionAdded(connection);
|
||||||
emit countChanged();
|
emit countChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +144,7 @@ void Connections::removeConnection(Connection *connection)
|
|||||||
beginRemoveRows(QModelIndex(), idx, idx);
|
beginRemoveRows(QModelIndex(), idx, idx);
|
||||||
m_connections.takeAt(idx)->deleteLater();
|
m_connections.takeAt(idx)->deleteLater();
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
|
emit connectionRemoved(connection);
|
||||||
emit countChanged();
|
emit countChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +168,25 @@ Connection* Connections::get(int index) const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connection *Connections::bestMatch(Connection::BearerTypes bearerTypes) const
|
||||||
|
{
|
||||||
|
Connection *best = nullptr;
|
||||||
|
foreach (Connection *c, m_connections) {
|
||||||
|
// qDebug() << "have connection:" << bearerTypes << c->url() << bearerTypes.testFlag(c->bearerType());
|
||||||
|
if ((bearerTypes & c->bearerType()) == Connection::BearerTypeNone) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!best) {
|
||||||
|
best = c;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c->priority() > best->priority()) {
|
||||||
|
best = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> Connections::roleNames() const
|
QHash<int, QByteArray> Connections::roleNames() const
|
||||||
{
|
{
|
||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
@ -208,5 +238,38 @@ void Connection::setOnline(bool online)
|
|||||||
if (m_online != online) {
|
if (m_online != online) {
|
||||||
m_online = online;
|
m_online = online;
|
||||||
emit onlineChanged();
|
emit onlineChanged();
|
||||||
|
emit priorityChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Connection::priority() const
|
||||||
|
{
|
||||||
|
int prio = 0;
|
||||||
|
if (m_online) {
|
||||||
|
prio += 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(m_bearerType) {
|
||||||
|
case BearerTypeEthernet:
|
||||||
|
prio += 400;
|
||||||
|
break;
|
||||||
|
case BearerTypeWifi:
|
||||||
|
prio += 300;
|
||||||
|
break;
|
||||||
|
case BearerTypeBluetooth:
|
||||||
|
prio += 200;
|
||||||
|
break;
|
||||||
|
case BearerTypeCloud:
|
||||||
|
prio += 100;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
prio += 0;
|
||||||
|
}
|
||||||
|
if (m_secure) {
|
||||||
|
prio += 10;
|
||||||
|
}
|
||||||
|
if (m_url.scheme().startsWith("nymea")) {
|
||||||
|
prio += 1;
|
||||||
|
}
|
||||||
|
return prio;
|
||||||
|
}
|
||||||
@ -18,8 +18,8 @@
|
|||||||
* *
|
* *
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
#ifndef DISCOVERYDEVICE_H
|
#ifndef NYMEAHOST_H
|
||||||
#define DISCOVERYDEVICE_H
|
#define NYMEAHOST_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
@ -36,15 +36,20 @@ class Connection: public QObject {
|
|||||||
Q_PROPERTY(bool secure READ secure CONSTANT)
|
Q_PROPERTY(bool secure READ secure CONSTANT)
|
||||||
Q_PROPERTY(QString displayName READ displayName CONSTANT)
|
Q_PROPERTY(QString displayName READ displayName CONSTANT)
|
||||||
Q_PROPERTY(bool online READ online NOTIFY onlineChanged)
|
Q_PROPERTY(bool online READ online NOTIFY onlineChanged)
|
||||||
|
Q_PROPERTY(int priority READ priority NOTIFY priorityChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum BearerType {
|
enum BearerType {
|
||||||
BearerTypeUnknown,
|
BearerTypeNone = 0x00,
|
||||||
BearerTypeWifi,
|
BearerTypeWifi = 0x01,
|
||||||
BearerTypeEthernet,
|
BearerTypeEthernet = 0x02,
|
||||||
BearerTypeBluetooth,
|
BearerTypeBluetooth = 0x04,
|
||||||
BearerTypeCloud
|
BearerTypeCloud = 0x08,
|
||||||
|
BearerTypeUnknown = 0xFF,
|
||||||
|
BearerTypeAll = 0xFF
|
||||||
};
|
};
|
||||||
Q_ENUM(BearerType)
|
Q_ENUM(BearerType)
|
||||||
|
Q_DECLARE_FLAGS(BearerTypes, BearerType)
|
||||||
|
|
||||||
Connection(const QUrl &url, BearerType bearerType, bool secure, const QString &displayName, QObject *parent = nullptr);
|
Connection(const QUrl &url, BearerType bearerType, bool secure, const QString &displayName, QObject *parent = nullptr);
|
||||||
|
|
||||||
@ -54,13 +59,15 @@ public:
|
|||||||
QString displayName() const;
|
QString displayName() const;
|
||||||
bool online() const;
|
bool online() const;
|
||||||
void setOnline(bool online);
|
void setOnline(bool online);
|
||||||
|
int priority() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void onlineChanged();
|
void onlineChanged();
|
||||||
|
void priorityChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QUrl m_url;
|
QUrl m_url;
|
||||||
BearerType m_bearerType = BearerTypeUnknown;
|
BearerType m_bearerType = BearerTypeNone;
|
||||||
bool m_secure = false;
|
bool m_secure = false;
|
||||||
QString m_displayName;
|
QString m_displayName;
|
||||||
bool m_online = false;
|
bool m_online = false;
|
||||||
@ -89,19 +96,22 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE Connection* find(const QUrl &url) const;
|
Q_INVOKABLE Connection* find(const QUrl &url) const;
|
||||||
Q_INVOKABLE Connection* get(int index) const;
|
Q_INVOKABLE Connection* get(int index) const;
|
||||||
|
Q_INVOKABLE Connection* bestMatch(Connection::BearerTypes bearerTypes = Connection::BearerTypeAll) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void countChanged();
|
void countChanged();
|
||||||
|
void connectionAdded(Connection *connection);
|
||||||
|
void connectionRemoved(Connection *connection);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<Connection*> m_connections;
|
QList<Connection*> m_connections;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
Q_DECLARE_OPERATORS_FOR_FLAGS(Connection::BearerTypes)
|
||||||
|
|
||||||
class DiscoveryDevice: public QObject
|
class NymeaHost: public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QUuid uuid READ uuid CONSTANT)
|
Q_PROPERTY(QUuid uuid READ uuid CONSTANT)
|
||||||
@ -110,7 +120,7 @@ class DiscoveryDevice: public QObject
|
|||||||
Q_PROPERTY(Connections* connections READ connections CONSTANT)
|
Q_PROPERTY(Connections* connections READ connections CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DiscoveryDevice(QObject *parent = nullptr);
|
explicit NymeaHost(QObject *parent = nullptr);
|
||||||
|
|
||||||
QUuid uuid() const;
|
QUuid uuid() const;
|
||||||
void setUuid(const QUuid &uuid);
|
void setUuid(const QUuid &uuid);
|
||||||
@ -126,6 +136,7 @@ public:
|
|||||||
signals:
|
signals:
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
void versionChanged();
|
void versionChanged();
|
||||||
|
void connectionChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QUuid m_uuid;
|
QUuid m_uuid;
|
||||||
@ -134,4 +145,4 @@ private:
|
|||||||
Connections *m_connections = nullptr;
|
Connections *m_connections = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DISCOVERYDEVICE_H
|
#endif // NYMEAHOST_H
|
||||||
220
libnymea-app-core/connection/nymeahosts.cpp
Normal file
220
libnymea-app-core/connection/nymeahosts.cpp
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2015 Simon Stuerz <stuerz.simon@gmail.com> *
|
||||||
|
* *
|
||||||
|
* This file is part of nymea:app. *
|
||||||
|
* *
|
||||||
|
* nymea:app is free software: you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation, version 3 of the License. *
|
||||||
|
* *
|
||||||
|
* nymea:app is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* GNU General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU General Public License *
|
||||||
|
* along with nymea:app. If not, see <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "nymeahosts.h"
|
||||||
|
#include "connection/discovery/nymeadiscovery.h"
|
||||||
|
#include "nymeahost.h"
|
||||||
|
#include "connection/nymeaconnection.h"
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
NymeaHosts::NymeaHosts(QObject *parent) :
|
||||||
|
QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int NymeaHosts::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
return m_hosts.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant NymeaHosts::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (index.row() < 0 || index.row() >= m_hosts.count())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
|
NymeaHost *host = m_hosts.at(index.row());
|
||||||
|
switch (role) {
|
||||||
|
case UuidRole:
|
||||||
|
return host->uuid();
|
||||||
|
case NameRole:
|
||||||
|
return host->name();
|
||||||
|
case VersionRole:
|
||||||
|
return host->version();
|
||||||
|
}
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaHosts::addHost(NymeaHost *host)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_hosts.count(); i++) {
|
||||||
|
if (m_hosts.at(i)->uuid() == host->uuid()) {
|
||||||
|
qWarning() << "Host already added. Update existing host instead.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
host->setParent(this);
|
||||||
|
connect(host, &NymeaHost::connectionChanged, this, &NymeaHosts::hostChanged);
|
||||||
|
|
||||||
|
beginInsertRows(QModelIndex(), m_hosts.count(), m_hosts.count());
|
||||||
|
m_hosts.append(host);
|
||||||
|
endInsertRows();
|
||||||
|
emit hostAdded(host);
|
||||||
|
emit countChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaHosts::removeHost(NymeaHost *host)
|
||||||
|
{
|
||||||
|
int idx = m_hosts.indexOf(host);
|
||||||
|
if (idx == -1) {
|
||||||
|
qWarning() << "Cannot remove NymeaHost" << host << "as its nit in the model";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
beginRemoveRows(QModelIndex(), idx, idx);
|
||||||
|
m_hosts.takeAt(idx);
|
||||||
|
endRemoveRows();
|
||||||
|
emit hostRemoved(host);
|
||||||
|
emit countChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
NymeaHost *NymeaHosts::createHost(const QString &name, const QUrl &url, Connection::BearerType bearerType)
|
||||||
|
{
|
||||||
|
NymeaHost *host = new NymeaHost(this);
|
||||||
|
host->setName(name);
|
||||||
|
Connection *connection = new Connection(url, bearerType, false, url.toString(), host);
|
||||||
|
host->connections()->addConnection(connection);
|
||||||
|
addHost(host);
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
NymeaHost *NymeaHosts::get(int index) const
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= m_hosts.count()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return m_hosts.at(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
NymeaHost *NymeaHosts::find(const QUuid &uuid)
|
||||||
|
{
|
||||||
|
foreach (NymeaHost *dev, m_hosts) {
|
||||||
|
if (dev->uuid() == uuid) {
|
||||||
|
return dev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaHosts::clearModel()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
m_hosts.clear();
|
||||||
|
endResetModel();
|
||||||
|
emit countChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> NymeaHosts::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roles;
|
||||||
|
roles[UuidRole] = "uuid";
|
||||||
|
roles[NameRole] = "name";
|
||||||
|
roles[VersionRole] = "version";
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
NymeaHostsFilterModel::NymeaHostsFilterModel(QObject *parent):
|
||||||
|
QSortFilterProxyModel(parent)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NymeaDiscovery *NymeaHostsFilterModel::discovery() const
|
||||||
|
{
|
||||||
|
return m_nymeaDiscovery;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaHostsFilterModel::setDiscovery(NymeaDiscovery *discovery)
|
||||||
|
{
|
||||||
|
if (m_nymeaDiscovery != discovery) {
|
||||||
|
m_nymeaDiscovery = discovery;
|
||||||
|
setSourceModel(discovery->nymeaHosts());
|
||||||
|
emit discoveryChanged();
|
||||||
|
|
||||||
|
connect(discovery->nymeaHosts(), &NymeaHosts::hostChanged, this, [this](){
|
||||||
|
// qDebug() << "Host Changed!";
|
||||||
|
invalidateFilter();
|
||||||
|
emit countChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
emit countChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NymeaConnection *NymeaHostsFilterModel::nymeaConnection() const
|
||||||
|
{
|
||||||
|
return m_nymeaConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaHostsFilterModel::setNymeaConnection(NymeaConnection *nymeaConnection)
|
||||||
|
{
|
||||||
|
if (m_nymeaConnection != nymeaConnection) {
|
||||||
|
m_nymeaConnection = nymeaConnection;
|
||||||
|
emit nymeaConnectionChanged();
|
||||||
|
|
||||||
|
connect(m_nymeaConnection, &NymeaConnection::availableBearerTypesChanged, this, [this](){
|
||||||
|
// qDebug() << "Bearer Types Changed!";
|
||||||
|
invalidateFilter();
|
||||||
|
emit countChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
invalidateFilter();
|
||||||
|
emit countChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NymeaHostsFilterModel::showUnreachableBearers() const
|
||||||
|
{
|
||||||
|
return m_showUneachableBearers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NymeaHostsFilterModel::setShowUnreachableBearers(bool showUnreachableBearers)
|
||||||
|
{
|
||||||
|
if (m_showUneachableBearers != showUnreachableBearers) {
|
||||||
|
m_showUneachableBearers = showUnreachableBearers;
|
||||||
|
emit showUnreachableBearersChanged();
|
||||||
|
invalidateFilter();
|
||||||
|
emit countChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NymeaHost *NymeaHostsFilterModel::get(int index) const
|
||||||
|
{
|
||||||
|
return m_nymeaDiscovery->nymeaHosts()->get(mapToSource(this->index(index, 0)).row());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NymeaHostsFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(sourceParent)
|
||||||
|
NymeaHost *host = m_nymeaDiscovery->nymeaHosts()->get(sourceRow);
|
||||||
|
if (m_nymeaConnection && !m_showUneachableBearers) {
|
||||||
|
bool hasReachableConnection = false;
|
||||||
|
for (int i = 0; i < host->connections()->rowCount(); i++) {
|
||||||
|
// qDebug() << "checking host for available bearer" << host->name() << host->connections()->get(i)->url() << "available bearer types:" << m_nymeaConnection->availableBearerTypes() << "hosts bearer types" << host->connections()->get(i)->bearerType();
|
||||||
|
if (m_nymeaConnection->availableBearerTypes().testFlag(host->connections()->get(i)->bearerType())) {
|
||||||
|
hasReachableConnection = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasReachableConnection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
111
libnymea-app-core/connection/nymeahosts.h
Normal file
111
libnymea-app-core/connection/nymeahosts.h
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
* *
|
||||||
|
* Copyright (C) 2015 Simon Stuerz <stuerz.simon@gmail.com> *
|
||||||
|
* *
|
||||||
|
* This file is part of nymea:app. *
|
||||||
|
* *
|
||||||
|
* nymea:app is free software: you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation, version 3 of the License. *
|
||||||
|
* *
|
||||||
|
* nymea:app is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* GNU General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU General Public License *
|
||||||
|
* along with nymea:app. If not, see <http://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef NYMEAHOSTS_H
|
||||||
|
#define NYMEAHOSTS_H
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QList>
|
||||||
|
#include <QBluetoothAddress>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
#include "nymeahost.h"
|
||||||
|
|
||||||
|
class NymeaDiscovery;
|
||||||
|
class NymeaConnection;
|
||||||
|
|
||||||
|
class NymeaHosts : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||||
|
public:
|
||||||
|
enum HostRole {
|
||||||
|
UuidRole,
|
||||||
|
NameRole,
|
||||||
|
VersionRole
|
||||||
|
};
|
||||||
|
Q_ENUM(HostRole)
|
||||||
|
|
||||||
|
explicit NymeaHosts(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex & parent = QModelIndex()) const;
|
||||||
|
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
|
||||||
|
|
||||||
|
void addHost(NymeaHost *host);
|
||||||
|
void removeHost(NymeaHost *host);
|
||||||
|
Q_INVOKABLE NymeaHost* createHost(const QString &name, const QUrl &url, Connection::BearerType bearerType);
|
||||||
|
|
||||||
|
Q_INVOKABLE NymeaHost *get(int index) const;
|
||||||
|
Q_INVOKABLE NymeaHost *find(const QUuid &uuid);
|
||||||
|
|
||||||
|
void clearModel();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void hostAdded(NymeaHost* host);
|
||||||
|
void hostRemoved(NymeaHost* host);
|
||||||
|
void countChanged();
|
||||||
|
void hostChanged();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QHash<int, QByteArray> roleNames() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<NymeaHost*> m_hosts;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NymeaHostsFilterModel: public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||||
|
Q_PROPERTY(NymeaDiscovery* discovery READ discovery WRITE setDiscovery NOTIFY discoveryChanged)
|
||||||
|
Q_PROPERTY(NymeaConnection* nymeaConnection READ nymeaConnection WRITE setNymeaConnection NOTIFY nymeaConnectionChanged)
|
||||||
|
Q_PROPERTY(bool showUnreachableBearers READ showUnreachableBearers WRITE setShowUnreachableBearers NOTIFY showUnreachableBearersChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
NymeaHostsFilterModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
NymeaDiscovery* discovery() const;
|
||||||
|
void setDiscovery(NymeaDiscovery *discovery);
|
||||||
|
|
||||||
|
NymeaConnection* nymeaConnection() const;
|
||||||
|
void setNymeaConnection(NymeaConnection* nymeaConnection);
|
||||||
|
|
||||||
|
bool showUnreachableBearers() const;
|
||||||
|
void setShowUnreachableBearers(bool showUnreachableBearers);
|
||||||
|
|
||||||
|
Q_INVOKABLE NymeaHost* get(int index) const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void countChanged();
|
||||||
|
void discoveryChanged();
|
||||||
|
void nymeaConnectionChanged();
|
||||||
|
void showUnreachableBearersChanged();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
NymeaDiscovery *m_nymeaDiscovery = nullptr;
|
||||||
|
NymeaConnection *m_nymeaConnection = nullptr;
|
||||||
|
|
||||||
|
bool m_showUneachableBearers = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NYMEAHOSTS_H
|
||||||
@ -53,6 +53,7 @@ public:
|
|||||||
virtual ~NymeaTransportInterface() = default;
|
virtual ~NymeaTransportInterface() = default;
|
||||||
|
|
||||||
virtual bool connect(const QUrl &url) = 0;
|
virtual bool connect(const QUrl &url) = 0;
|
||||||
|
virtual QUrl url() const = 0;
|
||||||
virtual void disconnect() = 0;
|
virtual void disconnect() = 0;
|
||||||
virtual ConnectionState connectionState() const = 0;
|
virtual ConnectionState connectionState() const = 0;
|
||||||
virtual void sendData(const QByteArray &data) = 0;
|
virtual void sendData(const QByteArray &data) = 0;
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
TcpSocketTransport::TcpSocketTransport(QObject *parent) : NymeaTransportInterface(parent)
|
TcpSocketTransport::TcpSocketTransport(QObject *parent) : NymeaTransportInterface(parent)
|
||||||
{
|
{
|
||||||
QObject::connect(&m_socket, &QSslSocket::connected, this, &TcpSocketTransport::onConnected);
|
QObject::connect(&m_socket, &QSslSocket::connected, this, &TcpSocketTransport::onConnected);
|
||||||
QObject::connect(&m_socket, &QSslSocket::disconnected, this, &TcpSocketTransport::disconnected);
|
|
||||||
QObject::connect(&m_socket, &QSslSocket::encrypted, this, &TcpSocketTransport::onEncrypted);
|
QObject::connect(&m_socket, &QSslSocket::encrypted, this, &TcpSocketTransport::onEncrypted);
|
||||||
typedef void (QSslSocket:: *sslErrorsSignal)(const QList<QSslError> &);
|
typedef void (QSslSocket:: *sslErrorsSignal)(const QList<QSslError> &);
|
||||||
QObject::connect(&m_socket, static_cast<sslErrorsSignal>(&QSslSocket::sslErrors), this, &TcpSocketTransport::sslErrors);
|
QObject::connect(&m_socket, static_cast<sslErrorsSignal>(&QSslSocket::sslErrors), this, &TcpSocketTransport::sslErrors);
|
||||||
@ -58,6 +57,11 @@ bool TcpSocketTransport::connect(const QUrl &url)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl TcpSocketTransport::url() const
|
||||||
|
{
|
||||||
|
return m_url;
|
||||||
|
}
|
||||||
|
|
||||||
NymeaTransportInterface::ConnectionState TcpSocketTransport::connectionState() const
|
NymeaTransportInterface::ConnectionState TcpSocketTransport::connectionState() const
|
||||||
{
|
{
|
||||||
switch (m_socket.state()) {
|
switch (m_socket.state()) {
|
||||||
@ -91,6 +95,9 @@ void TcpSocketTransport::socketReadyRead()
|
|||||||
void TcpSocketTransport::onSocketStateChanged(const QAbstractSocket::SocketState &state)
|
void TcpSocketTransport::onSocketStateChanged(const QAbstractSocket::SocketState &state)
|
||||||
{
|
{
|
||||||
qDebug() << "Socket state changed -->" << state;
|
qDebug() << "Socket state changed -->" << state;
|
||||||
|
if (state == QAbstractSocket::UnconnectedState) {
|
||||||
|
emit disconnected();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NymeaTransportInterface *TcpSocketTransportFactory::createTransport(QObject *parent) const
|
NymeaTransportInterface *TcpSocketTransportFactory::createTransport(QObject *parent) const
|
||||||
|
|||||||
@ -21,6 +21,7 @@ public:
|
|||||||
explicit TcpSocketTransport(QObject *parent = nullptr);
|
explicit TcpSocketTransport(QObject *parent = nullptr);
|
||||||
|
|
||||||
bool connect(const QUrl &url) override;
|
bool connect(const QUrl &url) override;
|
||||||
|
QUrl url() const override;
|
||||||
ConnectionState connectionState() const override;
|
ConnectionState connectionState() const override;
|
||||||
void disconnect() override;
|
void disconnect() override;
|
||||||
void sendData(const QByteArray &data) override;
|
void sendData(const QByteArray &data) override;
|
||||||
|
|||||||
@ -42,10 +42,16 @@ WebsocketTransport::WebsocketTransport(QObject *parent) :
|
|||||||
|
|
||||||
bool WebsocketTransport::connect(const QUrl &url)
|
bool WebsocketTransport::connect(const QUrl &url)
|
||||||
{
|
{
|
||||||
|
m_url = url;
|
||||||
m_socket->open(QUrl(url));
|
m_socket->open(QUrl(url));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl WebsocketTransport::url() const
|
||||||
|
{
|
||||||
|
return m_url;
|
||||||
|
}
|
||||||
|
|
||||||
NymeaTransportInterface::ConnectionState WebsocketTransport::connectionState() const
|
NymeaTransportInterface::ConnectionState WebsocketTransport::connectionState() const
|
||||||
{
|
{
|
||||||
switch (m_socket->state()) {
|
switch (m_socket->state()) {
|
||||||
|
|||||||
@ -40,12 +40,14 @@ public:
|
|||||||
explicit WebsocketTransport(QObject *parent = nullptr);
|
explicit WebsocketTransport(QObject *parent = nullptr);
|
||||||
|
|
||||||
bool connect(const QUrl &url) override;
|
bool connect(const QUrl &url) override;
|
||||||
|
QUrl url() const override;
|
||||||
ConnectionState connectionState() const override;
|
ConnectionState connectionState() const override;
|
||||||
void disconnect() override;
|
void disconnect() override;
|
||||||
void sendData(const QByteArray &data) override;
|
void sendData(const QByteArray &data) override;
|
||||||
void ignoreSslErrors(const QList<QSslError> &errors) override;
|
void ignoreSslErrors(const QList<QSslError> &errors) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QUrl m_url;
|
||||||
QWebSocket *m_socket;
|
QWebSocket *m_socket;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|||||||
@ -1,113 +0,0 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
* *
|
|
||||||
* Copyright (C) 2015 Simon Stuerz <stuerz.simon@gmail.com> *
|
|
||||||
* *
|
|
||||||
* This file is part of nymea:app. *
|
|
||||||
* *
|
|
||||||
* nymea:app is free software: you can redistribute it and/or modify *
|
|
||||||
* it under the terms of the GNU General Public License as published by *
|
|
||||||
* the Free Software Foundation, version 3 of the License. *
|
|
||||||
* *
|
|
||||||
* nymea:app is distributed in the hope that it will be useful, *
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
||||||
* GNU General Public License for more details. *
|
|
||||||
* *
|
|
||||||
* You should have received a copy of the GNU General Public License *
|
|
||||||
* along with nymea:app. If not, see <http://www.gnu.org/licenses/>. *
|
|
||||||
* *
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
#include "discoverymodel.h"
|
|
||||||
#include "discoverydevice.h"
|
|
||||||
|
|
||||||
DiscoveryModel::DiscoveryModel(QObject *parent) :
|
|
||||||
QAbstractListModel(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
int DiscoveryModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
Q_UNUSED(parent)
|
|
||||||
return m_devices.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant DiscoveryModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (index.row() < 0 || index.row() >= m_devices.count())
|
|
||||||
return QVariant();
|
|
||||||
|
|
||||||
DiscoveryDevice *device = m_devices.at(index.row());
|
|
||||||
switch (role) {
|
|
||||||
case UuidRole:
|
|
||||||
return device->uuid();
|
|
||||||
case NameRole:
|
|
||||||
return device->name();
|
|
||||||
case VersionRole:
|
|
||||||
return device->version();
|
|
||||||
}
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DiscoveryModel::addDevice(DiscoveryDevice *device)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < m_devices.count(); i++) {
|
|
||||||
if (m_devices.at(i)->uuid() == device->uuid()) {
|
|
||||||
qWarning() << "Device already added. Update existing device instead.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
device->setParent(this);
|
|
||||||
beginInsertRows(QModelIndex(), m_devices.count(), m_devices.count());
|
|
||||||
m_devices.append(device);
|
|
||||||
endInsertRows();
|
|
||||||
emit countChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DiscoveryModel::removeDevice(DiscoveryDevice *device)
|
|
||||||
{
|
|
||||||
int idx = m_devices.indexOf(device);
|
|
||||||
if (idx == -1) {
|
|
||||||
qWarning() << "Cannot remove DiscoveryDevice" << device << "as its nit in the model";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
beginRemoveRows(QModelIndex(), idx, idx);
|
|
||||||
m_devices.takeAt(idx);
|
|
||||||
endRemoveRows();
|
|
||||||
emit countChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
DiscoveryDevice *DiscoveryModel::get(int index) const
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= m_devices.count()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return m_devices.at(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
DiscoveryDevice *DiscoveryModel::find(const QUuid &uuid)
|
|
||||||
{
|
|
||||||
foreach (DiscoveryDevice *dev, m_devices) {
|
|
||||||
if (dev->uuid() == uuid) {
|
|
||||||
return dev;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DiscoveryModel::clearModel()
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
m_devices.clear();
|
|
||||||
endResetModel();
|
|
||||||
emit countChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> DiscoveryModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> roles;
|
|
||||||
roles[UuidRole] = "uuid";
|
|
||||||
roles[NameRole] = "name";
|
|
||||||
roles[VersionRole] = "version";
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
* *
|
|
||||||
* Copyright (C) 2015 Simon Stuerz <stuerz.simon@gmail.com> *
|
|
||||||
* *
|
|
||||||
* This file is part of nymea:app. *
|
|
||||||
* *
|
|
||||||
* nymea:app is free software: you can redistribute it and/or modify *
|
|
||||||
* it under the terms of the GNU General Public License as published by *
|
|
||||||
* the Free Software Foundation, version 3 of the License. *
|
|
||||||
* *
|
|
||||||
* nymea:app is distributed in the hope that it will be useful, *
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
||||||
* GNU General Public License for more details. *
|
|
||||||
* *
|
|
||||||
* You should have received a copy of the GNU General Public License *
|
|
||||||
* along with nymea:app. If not, see <http://www.gnu.org/licenses/>. *
|
|
||||||
* *
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
#ifndef DISCOVERYMODEL_H
|
|
||||||
#define DISCOVERYMODEL_H
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QList>
|
|
||||||
#include <QBluetoothAddress>
|
|
||||||
|
|
||||||
class DiscoveryDevice;
|
|
||||||
|
|
||||||
class DiscoveryModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
|
||||||
public:
|
|
||||||
enum DeviceRole {
|
|
||||||
DeviceTypeRole,
|
|
||||||
UuidRole,
|
|
||||||
NameRole,
|
|
||||||
VersionRole
|
|
||||||
};
|
|
||||||
Q_ENUM(DeviceRole)
|
|
||||||
|
|
||||||
explicit DiscoveryModel(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
int rowCount(const QModelIndex & parent = QModelIndex()) const;
|
|
||||||
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
|
|
||||||
|
|
||||||
void addDevice(DiscoveryDevice *device);
|
|
||||||
void removeDevice(DiscoveryDevice *device);
|
|
||||||
|
|
||||||
Q_INVOKABLE DiscoveryDevice *get(int index) const;
|
|
||||||
Q_INVOKABLE DiscoveryDevice *find(const QUuid &uuid);
|
|
||||||
|
|
||||||
void clearModel();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void countChanged();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QHash<int, QByteArray> roleNames() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QList<DiscoveryDevice *> m_devices;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // DISCOVERYMODEL_H
|
|
||||||
@ -1,148 +0,0 @@
|
|||||||
#include "nymeadiscovery.h"
|
|
||||||
#include "upnpdiscovery.h"
|
|
||||||
#include "zeroconfdiscovery.h"
|
|
||||||
#include "bluetoothservicediscovery.h"
|
|
||||||
#include "connection/awsclient.h"
|
|
||||||
|
|
||||||
#include <QUuid>
|
|
||||||
#include <QBluetoothUuid>
|
|
||||||
#include <QUrlQuery>
|
|
||||||
|
|
||||||
NymeaDiscovery::NymeaDiscovery(QObject *parent) : QObject(parent)
|
|
||||||
{
|
|
||||||
m_discoveryModel = new DiscoveryModel(this);
|
|
||||||
|
|
||||||
m_upnp = new UpnpDiscovery(m_discoveryModel, this);
|
|
||||||
m_zeroConf = new ZeroconfDiscovery(m_discoveryModel, this);
|
|
||||||
|
|
||||||
#ifndef Q_OS_IOS
|
|
||||||
m_bluetooth = new BluetoothServiceDiscovery(m_discoveryModel, this);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_cloudPollTimer.setInterval(5000);
|
|
||||||
connect(&m_cloudPollTimer, &QTimer::timeout, this, [this](){
|
|
||||||
if (m_awsClient && m_awsClient->isLoggedIn()) {
|
|
||||||
m_awsClient->fetchDevices();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NymeaDiscovery::discovering() const
|
|
||||||
{
|
|
||||||
return m_discovering;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NymeaDiscovery::setDiscovering(bool discovering)
|
|
||||||
{
|
|
||||||
if (m_discovering == discovering)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_discovering = discovering;
|
|
||||||
// If we have zeroconf skip upnp. ZeroConf will not do an active discovery and if it's available it'll always have good data
|
|
||||||
if (!m_zeroConf->available()) {
|
|
||||||
if (discovering) {
|
|
||||||
m_upnp->discover();
|
|
||||||
} else {
|
|
||||||
m_upnp->stopDiscovery();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (discovering) {
|
|
||||||
// If there's no Zeroconf, use UPnP instead
|
|
||||||
if (!m_zeroConf->available()) {
|
|
||||||
m_upnp->discover();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always start Bluetooth discovery if HW is available
|
|
||||||
if (m_bluetooth) {
|
|
||||||
m_bluetooth->discover();
|
|
||||||
}
|
|
||||||
|
|
||||||
// start polling cloud
|
|
||||||
m_cloudPollTimer.start();
|
|
||||||
// If we're logged in, poll right away
|
|
||||||
if (m_awsClient && m_awsClient->isLoggedIn()) {
|
|
||||||
syncCloudDevices();
|
|
||||||
m_awsClient->fetchDevices();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!m_zeroConf->available()) {
|
|
||||||
m_upnp->stopDiscovery();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_bluetooth) {
|
|
||||||
m_bluetooth->stopDiscovery();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_cloudPollTimer.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
emit discoveringChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
DiscoveryModel *NymeaDiscovery::discoveryModel() const
|
|
||||||
{
|
|
||||||
return m_discoveryModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
AWSClient *NymeaDiscovery::awsClient() const
|
|
||||||
{
|
|
||||||
return m_awsClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NymeaDiscovery::setAwsClient(AWSClient *awsClient)
|
|
||||||
{
|
|
||||||
if (m_awsClient != awsClient) {
|
|
||||||
m_awsClient = awsClient;
|
|
||||||
emit awsClientChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_awsClient) {
|
|
||||||
connect(m_awsClient, &AWSClient::devicesFetched, this, &NymeaDiscovery::syncCloudDevices);
|
|
||||||
syncCloudDevices();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NymeaDiscovery::syncCloudDevices()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < m_awsClient->awsDevices()->rowCount(); i++) {
|
|
||||||
AWSDevice *d = m_awsClient->awsDevices()->get(i);
|
|
||||||
DiscoveryDevice *device = m_discoveryModel->find(d->id());
|
|
||||||
if (!device) {
|
|
||||||
device = new DiscoveryDevice();
|
|
||||||
device->setUuid(d->id());
|
|
||||||
device->setName(d->name());
|
|
||||||
qDebug() << "CloudDiscovery: Adding new host:" << device->name() << device->uuid().toString();
|
|
||||||
m_discoveryModel->addDevice(device);
|
|
||||||
}
|
|
||||||
QUrl url;
|
|
||||||
url.setScheme("cloud");
|
|
||||||
url.setHost(d->id());
|
|
||||||
Connection *conn = device->connections()->find(url);
|
|
||||||
if (!conn) {
|
|
||||||
conn = new Connection(url, Connection::BearerTypeCloud, true, d->id());
|
|
||||||
qDebug() << "CloudDiscovery: Adding new connection to host:" << device->name() << conn->url().toString();
|
|
||||||
device->connections()->addConnection(conn);
|
|
||||||
}
|
|
||||||
conn->setOnline(d->online());
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<DiscoveryDevice*> devicesToRemove;
|
|
||||||
for (int i = 0; i < m_discoveryModel->rowCount(); i++) {
|
|
||||||
DiscoveryDevice *device = m_discoveryModel->get(i);
|
|
||||||
for (int j = 0; j < device->connections()->rowCount(); j++) {
|
|
||||||
if (device->connections()->get(j)->bearerType() == Connection::BearerTypeCloud) {
|
|
||||||
if (m_awsClient->awsDevices()->getDevice(device->uuid().toString()) == nullptr) {
|
|
||||||
device->connections()->removeConnection(j);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (device->connections()->rowCount() == 0) {
|
|
||||||
devicesToRemove.append(device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (!devicesToRemove.isEmpty()) {
|
|
||||||
m_discoveryModel->removeDevice(devicesToRemove.takeFirst());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
* *
|
|
||||||
* Copyright (C) 2015 Simon Stuerz <stuerz.simon@gmail.com> *
|
|
||||||
* *
|
|
||||||
* This file is part of nymea:app. *
|
|
||||||
* *
|
|
||||||
* nymea:app is free software: you can redistribute it and/or modify *
|
|
||||||
* it under the terms of the GNU General Public License as published by *
|
|
||||||
* the Free Software Foundation, version 3 of the License. *
|
|
||||||
* *
|
|
||||||
* nymea:app is distributed in the hope that it will be useful, *
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
||||||
* GNU General Public License for more details. *
|
|
||||||
* *
|
|
||||||
* You should have received a copy of the GNU General Public License *
|
|
||||||
* along with nymea:app. If not, see <http://www.gnu.org/licenses/>. *
|
|
||||||
* *
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
#include "nymeahost.h"
|
|
||||||
|
|
||||||
NymeaHost::NymeaHost(QObject *parent) :
|
|
||||||
QObject(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QString NymeaHost::name() const
|
|
||||||
{
|
|
||||||
return m_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NymeaHost::setName(const QString &name)
|
|
||||||
{
|
|
||||||
m_name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString NymeaHost::webSocketUrl() const
|
|
||||||
{
|
|
||||||
return m_webSocketUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NymeaHost::setWebSocketUrl(const QString &webSocketUrl)
|
|
||||||
{
|
|
||||||
m_webSocketUrl = webSocketUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString NymeaHost::hostAddress() const
|
|
||||||
{
|
|
||||||
return m_hostAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NymeaHost::setHostAddress(const QString &hostAddress)
|
|
||||||
{
|
|
||||||
m_hostAddress = hostAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
QUuid NymeaHost::uuid() const
|
|
||||||
{
|
|
||||||
return m_uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NymeaHost::setUuid(const QUuid &uuid)
|
|
||||||
{
|
|
||||||
m_uuid = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
* *
|
|
||||||
* Copyright (C) 2015 Simon Stuerz <stuerz.simon@gmail.com> *
|
|
||||||
* *
|
|
||||||
* This file is part of nymea:app. *
|
|
||||||
* *
|
|
||||||
* nymea:app is free software: you can redistribute it and/or modify *
|
|
||||||
* it under the terms of the GNU General Public License as published by *
|
|
||||||
* the Free Software Foundation, version 3 of the License. *
|
|
||||||
* *
|
|
||||||
* nymea:app is distributed in the hope that it will be useful, *
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
||||||
* GNU General Public License for more details. *
|
|
||||||
* *
|
|
||||||
* You should have received a copy of the GNU General Public License *
|
|
||||||
* along with nymea:app. If not, see <http://www.gnu.org/licenses/>. *
|
|
||||||
* *
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
#ifndef NYMEAHOST_H
|
|
||||||
#define NYMEAHOST_H
|
|
||||||
|
|
||||||
#include <QUuid>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QHostAddress>
|
|
||||||
|
|
||||||
class NymeaHost : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit NymeaHost(QObject *parent = 0);
|
|
||||||
|
|
||||||
QString name() const;
|
|
||||||
void setName(const QString &name);
|
|
||||||
|
|
||||||
QString webSocketUrl() const;
|
|
||||||
void setWebSocketUrl(const QString &webSocketUrl);
|
|
||||||
|
|
||||||
QString hostAddress() const;
|
|
||||||
void setHostAddress(const QString &hostAddress);
|
|
||||||
|
|
||||||
QUuid uuid() const;
|
|
||||||
void setUuid(const QUuid &uuid);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString m_name;
|
|
||||||
QString m_webSocketUrl;
|
|
||||||
QString m_hostAddress;
|
|
||||||
QUuid m_uuid;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // NYMEAHOST_H
|
|
||||||
@ -1,150 +0,0 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
* *
|
|
||||||
* Copyright (C) 2015 Simon Stuerz <stuerz.simon@gmail.com> *
|
|
||||||
* *
|
|
||||||
* This file is part of nymea:app. *
|
|
||||||
* *
|
|
||||||
* nymea:app is free software: you can redistribute it and/or modify *
|
|
||||||
* it under the terms of the GNU General Public License as published by *
|
|
||||||
* the Free Software Foundation, version 3 of the License. *
|
|
||||||
* *
|
|
||||||
* nymea:app is distributed in the hope that it will be useful, *
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
||||||
* GNU General Public License for more details. *
|
|
||||||
* *
|
|
||||||
* You should have received a copy of the GNU General Public License *
|
|
||||||
* along with nymea:app. If not, see <http://www.gnu.org/licenses/>. *
|
|
||||||
* *
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
#include "nymeahosts.h"
|
|
||||||
#include "nymeahost.h"
|
|
||||||
|
|
||||||
#include <QUuid>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QSettings>
|
|
||||||
|
|
||||||
NymeaHosts::NymeaHosts(QObject *parent) :
|
|
||||||
QAbstractListModel(parent)
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
QSettings settings;
|
|
||||||
qDebug() << "Connections: loading connections " << settings.fileName();
|
|
||||||
settings.beginGroup("Connections");
|
|
||||||
foreach (const QString &uuid, settings.childGroups()) {
|
|
||||||
settings.beginGroup(uuid);
|
|
||||||
NymeaHost *host = new NymeaHost(this);
|
|
||||||
host->setName(settings.value("name").toString());
|
|
||||||
host->setHostAddress(settings.value("hostAddress").toString());
|
|
||||||
host->setWebSocketUrl(settings.value("webSocketUrl").toString());
|
|
||||||
host->setUuid(QUuid(uuid));
|
|
||||||
qDebug() << " " << host->webSocketUrl();
|
|
||||||
m_hosts.append(host);
|
|
||||||
settings.endGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.endGroup();
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
NymeaHost *NymeaHosts::get(const QString &webSocketUrl)
|
|
||||||
{
|
|
||||||
foreach (NymeaHost *host, m_hosts) {
|
|
||||||
if (host->webSocketUrl() == webSocketUrl) {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<NymeaHost *> NymeaHosts::hosts()
|
|
||||||
{
|
|
||||||
return m_hosts;
|
|
||||||
}
|
|
||||||
|
|
||||||
int NymeaHosts::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
Q_UNUSED(parent)
|
|
||||||
return m_hosts.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant NymeaHosts::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (index.row() < 0 || index.row() >= m_hosts.count())
|
|
||||||
return QVariant();
|
|
||||||
|
|
||||||
NymeaHost *host = m_hosts.at(index.row());
|
|
||||||
if (role == NameRole) {
|
|
||||||
return host->name();
|
|
||||||
} else if (role == HostAddressRole) {
|
|
||||||
return host->hostAddress();
|
|
||||||
} else if (role == WebSocketUrlRole) {
|
|
||||||
return host->webSocketUrl();
|
|
||||||
}
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NymeaHosts::addHost(const QString &name, const QString &hostAddress, const QString &webSocketUrl)
|
|
||||||
{
|
|
||||||
// check if we allready have added this connection
|
|
||||||
foreach (NymeaHost *host, m_hosts) {
|
|
||||||
if (host->webSocketUrl() == webSocketUrl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NymeaHost *host = new NymeaHost(this);
|
|
||||||
host->setName(name);
|
|
||||||
host->setHostAddress(hostAddress);
|
|
||||||
host->setWebSocketUrl(webSocketUrl);
|
|
||||||
host->setUuid(QUuid::createUuid());
|
|
||||||
|
|
||||||
qDebug() << "NymeaHosts: add connection" << host->webSocketUrl();
|
|
||||||
beginInsertRows(QModelIndex(), m_hosts.count(), m_hosts.count());
|
|
||||||
m_hosts.append(host);
|
|
||||||
endInsertRows();
|
|
||||||
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup("Connections");
|
|
||||||
settings.beginGroup(host->uuid().toString());
|
|
||||||
settings.setValue("name", name);
|
|
||||||
settings.setValue("hostAddress", hostAddress);
|
|
||||||
settings.setValue("webSocketUrl", webSocketUrl);
|
|
||||||
settings.endGroup();
|
|
||||||
settings.endGroup();
|
|
||||||
qDebug() << "Connections: saved connection" << settings.fileName();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NymeaHosts::removeHost(NymeaHost *host)
|
|
||||||
{
|
|
||||||
int index = m_hosts.indexOf(host);
|
|
||||||
beginRemoveRows(QModelIndex(), index, index);
|
|
||||||
qDebug() << "Connections: removed connection" << host->webSocketUrl();
|
|
||||||
m_hosts.removeAt(index);
|
|
||||||
|
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup("Connections");
|
|
||||||
settings.remove(host->uuid().toString());
|
|
||||||
settings.endGroup();
|
|
||||||
host->deleteLater();
|
|
||||||
endRemoveRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NymeaHosts::clearModel()
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
qDebug() << "NymeaHosts: delete all hosts";
|
|
||||||
qDeleteAll(m_hosts);
|
|
||||||
m_hosts.clear();
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> NymeaHosts::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> roles;
|
|
||||||
roles[NameRole] = "name";
|
|
||||||
roles[HostAddressRole] = "hostAddress";
|
|
||||||
roles[WebSocketUrlRole] = "webSocketUrl";
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
* *
|
|
||||||
* Copyright (C) 2015 Simon Stuerz <stuerz.simon@gmail.com> *
|
|
||||||
* *
|
|
||||||
* This file is part of nymea:app. *
|
|
||||||
* *
|
|
||||||
* nymea:app is free software: you can redistribute it and/or modify *
|
|
||||||
* it under the terms of the GNU General Public License as published by *
|
|
||||||
* the Free Software Foundation, version 3 of the License. *
|
|
||||||
* *
|
|
||||||
* nymea:app is distributed in the hope that it will be useful, *
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
||||||
* GNU General Public License for more details. *
|
|
||||||
* *
|
|
||||||
* You should have received a copy of the GNU General Public License *
|
|
||||||
* along with nymea:app. If not, see <http://www.gnu.org/licenses/>. *
|
|
||||||
* *
|
|
||||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
||||||
|
|
||||||
#ifndef NYMEAHOSTS_H
|
|
||||||
#define NYMEAHOSTS_H
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
|
|
||||||
class NymeaHost;
|
|
||||||
|
|
||||||
class NymeaHosts : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum ConnectionRole {
|
|
||||||
NameRole = Qt::DisplayRole,
|
|
||||||
HostAddressRole,
|
|
||||||
WebSocketUrlRole
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit NymeaHosts(QObject *parent = 0);
|
|
||||||
|
|
||||||
Q_INVOKABLE NymeaHost *get(const QString &webSocketUrl);
|
|
||||||
QList<NymeaHost*> hosts();
|
|
||||||
|
|
||||||
int rowCount(const QModelIndex & parent = QModelIndex()) const;
|
|
||||||
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
|
|
||||||
|
|
||||||
void addHost(const QString &name, const QString &hostAddress, const QString &webSocketUrl);
|
|
||||||
Q_INVOKABLE void removeHost(NymeaHost *host);
|
|
||||||
|
|
||||||
void clearModel();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QHash<int, QByteArray> roleNames() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QList<NymeaHost*> m_hosts;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // NYMEAHOSTS_H
|
|
||||||
@ -363,6 +363,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data)
|
|||||||
if (!protoVersionString.contains('.')) {
|
if (!protoVersionString.contains('.')) {
|
||||||
protoVersionString.prepend("0.");
|
protoVersionString.prepend("0.");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_jsonRpcVersion = QVersionNumber::fromString(protoVersionString);
|
m_jsonRpcVersion = QVersionNumber::fromString(protoVersionString);
|
||||||
|
|
||||||
qDebug() << "Handshake reply:" << "Protocol version:" << protoVersionString << "InitRequired:" << m_initialSetupRequired << "AuthRequired:" << m_authenticationRequired << "PushButtonAvailable:" << m_pushButtonAuthAvailable;;
|
qDebug() << "Handshake reply:" << "Protocol version:" << protoVersionString << "InitRequired:" << m_initialSetupRequired << "AuthRequired:" << m_authenticationRequired << "PushButtonAvailable:" << m_pushButtonAuthAvailable;;
|
||||||
@ -377,6 +378,11 @@ void JsonRpcClient::dataReceived(const QByteArray &data)
|
|||||||
|
|
||||||
emit handshakeReceived();
|
emit handshakeReceived();
|
||||||
|
|
||||||
|
if (m_connection->currentHost()->uuid().isNull()) {
|
||||||
|
qDebug() << "Updating Server UUID in connection:" << m_connection->currentHost()->uuid().toString() << "->" << m_serverUuid;
|
||||||
|
m_connection->currentHost()->setUuid(m_serverUuid);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_initialSetupRequired) {
|
if (m_initialSetupRequired) {
|
||||||
emit initialSetupRequiredChanged();
|
emit initialSetupRequiredChanged();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -2,14 +2,14 @@
|
|||||||
#define LIBNYMEAAPPCORE_H
|
#define LIBNYMEAAPPCORE_H
|
||||||
|
|
||||||
#include "engine.h"
|
#include "engine.h"
|
||||||
|
#include "connection/nymeahosts.h"
|
||||||
|
#include "connection/nymeahost.h"
|
||||||
|
#include "connection/discovery/nymeadiscovery.h"
|
||||||
#include "vendorsproxy.h"
|
#include "vendorsproxy.h"
|
||||||
#include "deviceclassesproxy.h"
|
#include "deviceclassesproxy.h"
|
||||||
#include "devicesproxy.h"
|
#include "devicesproxy.h"
|
||||||
#include "pluginsproxy.h"
|
#include "pluginsproxy.h"
|
||||||
#include "devicediscovery.h"
|
#include "devicediscovery.h"
|
||||||
#include "discovery/nymeadiscovery.h"
|
|
||||||
#include "discovery/discoverymodel.h"
|
|
||||||
#include "discovery/discoverydevice.h"
|
|
||||||
#include "interfacesmodel.h"
|
#include "interfacesmodel.h"
|
||||||
#include "rulemanager.h"
|
#include "rulemanager.h"
|
||||||
#include "models/rulesfiltermodel.h"
|
#include "models/rulesfiltermodel.h"
|
||||||
@ -159,9 +159,10 @@ void registerQmlTypes() {
|
|||||||
qmlRegisterUncreatableType<MqttPolicies>(uri, 1, 0, "MqttPolicies", "Get it from NymeaConfiguration");
|
qmlRegisterUncreatableType<MqttPolicies>(uri, 1, 0, "MqttPolicies", "Get it from NymeaConfiguration");
|
||||||
|
|
||||||
qmlRegisterType<NymeaDiscovery>(uri, 1, 0, "NymeaDiscovery");
|
qmlRegisterType<NymeaDiscovery>(uri, 1, 0, "NymeaDiscovery");
|
||||||
qmlRegisterUncreatableType<DiscoveryModel>(uri, 1, 0, "DiscoveryModel", "Get it from NymeaDiscovery");
|
qmlRegisterUncreatableType<NymeaHosts>(uri, 1, 0, "NymeaHosts", "Get it from NymeaDiscovery");
|
||||||
qmlRegisterUncreatableType<DiscoveryDevice>(uri, 1, 0, "DiscoveryDevice", "Get it from DiscoveryModel");
|
qmlRegisterType<NymeaHostsFilterModel>(uri, 1, 0, "NymeaHostsFilterModel");
|
||||||
qmlRegisterUncreatableType<Connection>(uri, 1, 0, "Connection", "Get it from DiscoveryDevice");
|
qmlRegisterUncreatableType<NymeaHost>(uri, 1, 0, "NymeaHost", "Get it from NymeaHosts");
|
||||||
|
qmlRegisterUncreatableType<Connection>(uri, 1, 0, "Connection", "Get it from NymeaHost");
|
||||||
|
|
||||||
qmlRegisterType<LogsModel>(uri, 1, 0, "LogsModel");
|
qmlRegisterType<LogsModel>(uri, 1, 0, "LogsModel");
|
||||||
qmlRegisterType<LogsModelNg>(uri, 1, 0, "LogsModelNg");
|
qmlRegisterType<LogsModelNg>(uri, 1, 0, "LogsModelNg");
|
||||||
|
|||||||
@ -25,19 +25,22 @@ INCLUDEPATH += $$top_srcdir/libnymea-common \
|
|||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
engine.cpp \
|
engine.cpp \
|
||||||
|
connection/nymeahost.cpp \
|
||||||
|
connection/nymeahosts.cpp \
|
||||||
connection/nymeaconnection.cpp \
|
connection/nymeaconnection.cpp \
|
||||||
connection/nymeatransportinterface.cpp \
|
connection/nymeatransportinterface.cpp \
|
||||||
connection/websockettransport.cpp \
|
connection/websockettransport.cpp \
|
||||||
connection/tcpsockettransport.cpp \
|
connection/tcpsockettransport.cpp \
|
||||||
connection/bluetoothtransport.cpp \
|
connection/bluetoothtransport.cpp \
|
||||||
connection/awsclient.cpp \
|
connection/awsclient.cpp \
|
||||||
|
connection/discovery/nymeadiscovery.cpp \
|
||||||
|
connection/discovery/upnpdiscovery.cpp \
|
||||||
|
connection/discovery/zeroconfdiscovery.cpp \
|
||||||
|
connection/discovery/bluetoothservicediscovery.cpp \
|
||||||
devicemanager.cpp \
|
devicemanager.cpp \
|
||||||
jsonrpc/jsontypes.cpp \
|
jsonrpc/jsontypes.cpp \
|
||||||
jsonrpc/jsonrpcclient.cpp \
|
jsonrpc/jsonrpcclient.cpp \
|
||||||
jsonrpc/jsonhandler.cpp \
|
jsonrpc/jsonhandler.cpp \
|
||||||
discovery/nymeahost.cpp \
|
|
||||||
discovery/nymeahosts.cpp \
|
|
||||||
discovery/upnpdiscovery.cpp \
|
|
||||||
devices.cpp \
|
devices.cpp \
|
||||||
devicesproxy.cpp \
|
devicesproxy.cpp \
|
||||||
deviceclasses.cpp \
|
deviceclasses.cpp \
|
||||||
@ -46,14 +49,10 @@ SOURCES += \
|
|||||||
vendorsproxy.cpp \
|
vendorsproxy.cpp \
|
||||||
pluginsproxy.cpp \
|
pluginsproxy.cpp \
|
||||||
interfacesmodel.cpp \
|
interfacesmodel.cpp \
|
||||||
discovery/zeroconfdiscovery.cpp \
|
|
||||||
discovery/discoverydevice.cpp \
|
|
||||||
discovery/discoverymodel.cpp \
|
|
||||||
rulemanager.cpp \
|
rulemanager.cpp \
|
||||||
models/rulesfiltermodel.cpp \
|
models/rulesfiltermodel.cpp \
|
||||||
models/logsmodel.cpp \
|
models/logsmodel.cpp \
|
||||||
models/valuelogsproxymodel.cpp \
|
models/valuelogsproxymodel.cpp \
|
||||||
discovery/nymeadiscovery.cpp \
|
|
||||||
logmanager.cpp \
|
logmanager.cpp \
|
||||||
wifisetup/bluetoothdevice.cpp \
|
wifisetup/bluetoothdevice.cpp \
|
||||||
wifisetup/bluetoothdeviceinfo.cpp \
|
wifisetup/bluetoothdeviceinfo.cpp \
|
||||||
@ -74,7 +73,6 @@ SOURCES += \
|
|||||||
ruletemplates/ruleactiontemplate.cpp \
|
ruletemplates/ruleactiontemplate.cpp \
|
||||||
ruletemplates/stateevaluatortemplate.cpp \
|
ruletemplates/stateevaluatortemplate.cpp \
|
||||||
ruletemplates/statedescriptortemplate.cpp \
|
ruletemplates/statedescriptortemplate.cpp \
|
||||||
discovery/bluetoothservicediscovery.cpp \
|
|
||||||
connection/cloudtransport.cpp \
|
connection/cloudtransport.cpp \
|
||||||
connection/sigv4utils.cpp \
|
connection/sigv4utils.cpp \
|
||||||
ruletemplates/ruleactionparamtemplate.cpp \
|
ruletemplates/ruleactionparamtemplate.cpp \
|
||||||
@ -88,6 +86,8 @@ SOURCES += \
|
|||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
engine.h \
|
engine.h \
|
||||||
|
connection/nymeahost.h \
|
||||||
|
connection/nymeahosts.h \
|
||||||
connection/nymeaconnection.h \
|
connection/nymeaconnection.h \
|
||||||
connection/nymeatransportinterface.h \
|
connection/nymeatransportinterface.h \
|
||||||
connection/websockettransport.h \
|
connection/websockettransport.h \
|
||||||
@ -95,13 +95,14 @@ HEADERS += \
|
|||||||
connection/bluetoothtransport.h \
|
connection/bluetoothtransport.h \
|
||||||
connection/awsclient.h \
|
connection/awsclient.h \
|
||||||
connection/sigv4utils.h \
|
connection/sigv4utils.h \
|
||||||
|
connection/discovery/nymeadiscovery.h \
|
||||||
|
connection/discovery/upnpdiscovery.h \
|
||||||
|
connection/discovery/zeroconfdiscovery.h \
|
||||||
|
connection/discovery/bluetoothservicediscovery.h \
|
||||||
devicemanager.h \
|
devicemanager.h \
|
||||||
jsonrpc/jsontypes.h \
|
jsonrpc/jsontypes.h \
|
||||||
jsonrpc/jsonrpcclient.h \
|
jsonrpc/jsonrpcclient.h \
|
||||||
jsonrpc/jsonhandler.h \
|
jsonrpc/jsonhandler.h \
|
||||||
discovery/nymeahost.h \
|
|
||||||
discovery/nymeahosts.h \
|
|
||||||
discovery/upnpdiscovery.h \
|
|
||||||
devices.h \
|
devices.h \
|
||||||
devicesproxy.h \
|
devicesproxy.h \
|
||||||
deviceclasses.h \
|
deviceclasses.h \
|
||||||
@ -110,14 +111,10 @@ HEADERS += \
|
|||||||
vendorsproxy.h \
|
vendorsproxy.h \
|
||||||
pluginsproxy.h \
|
pluginsproxy.h \
|
||||||
interfacesmodel.h \
|
interfacesmodel.h \
|
||||||
discovery/zeroconfdiscovery.h \
|
|
||||||
discovery/discoverydevice.h \
|
|
||||||
discovery/discoverymodel.h \
|
|
||||||
rulemanager.h \
|
rulemanager.h \
|
||||||
models/rulesfiltermodel.h \
|
models/rulesfiltermodel.h \
|
||||||
models/logsmodel.h \
|
models/logsmodel.h \
|
||||||
models/valuelogsproxymodel.h \
|
models/valuelogsproxymodel.h \
|
||||||
discovery/nymeadiscovery.h \
|
|
||||||
logmanager.h \
|
logmanager.h \
|
||||||
wifisetup/bluetoothdevice.h \
|
wifisetup/bluetoothdevice.h \
|
||||||
wifisetup/bluetoothdeviceinfo.h \
|
wifisetup/bluetoothdeviceinfo.h \
|
||||||
@ -139,7 +136,6 @@ HEADERS += \
|
|||||||
ruletemplates/ruleactiontemplate.h \
|
ruletemplates/ruleactiontemplate.h \
|
||||||
ruletemplates/stateevaluatortemplate.h \
|
ruletemplates/stateevaluatortemplate.h \
|
||||||
ruletemplates/statedescriptortemplate.h \
|
ruletemplates/statedescriptortemplate.h \
|
||||||
discovery/bluetoothservicediscovery.h \
|
|
||||||
connection/cloudtransport.h \
|
connection/cloudtransport.h \
|
||||||
ruletemplates/ruleactionparamtemplate.h \
|
ruletemplates/ruleactionparamtemplate.h \
|
||||||
configuration/serverconfiguration.h \
|
configuration/serverconfiguration.h \
|
||||||
|
|||||||
@ -57,6 +57,12 @@ QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
|
|||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
|
||||||
|
QLoggingCategory::setFilterRules("RemoteProxyClientJsonRpcTraffic.debug=false\n"
|
||||||
|
"RemoteProxyClientJsonRpc.debug=false\n"
|
||||||
|
"RemoteProxyClientWebSocket.debug=false\n"
|
||||||
|
"RemoteProxyClientConnection.debug=false\n"
|
||||||
|
"RemoteProxyClientConnectionTraffic.debug=false\n"
|
||||||
|
);
|
||||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||||
QApplication application(argc, argv);
|
QApplication application(argc, argv);
|
||||||
application.setApplicationName("nymea-app");
|
application.setApplicationName("nymea-app");
|
||||||
@ -119,8 +125,5 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
engine->load(QUrl(QLatin1String("qrc:/ui/Nymea.qml")));
|
engine->load(QUrl(QLatin1String("qrc:/ui/Nymea.qml")));
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
QtAndroid::hideSplashScreen(250);
|
|
||||||
#endif
|
|
||||||
return application.exec();
|
return application.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,8 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE virtual void requestPermissions() = 0;
|
Q_INVOKABLE virtual void requestPermissions() = 0;
|
||||||
|
|
||||||
|
Q_INVOKABLE virtual void hideSplashScreen() = 0;
|
||||||
|
|
||||||
virtual bool hasPermissions() const = 0;
|
virtual bool hasPermissions() const = 0;
|
||||||
virtual QString machineHostname() const = 0;
|
virtual QString machineHostname() const = 0;
|
||||||
virtual QString deviceSerial() const = 0;
|
virtual QString deviceSerial() const = 0;
|
||||||
|
|||||||
@ -16,6 +16,16 @@ void PlatformHelperAndroid::requestPermissions()
|
|||||||
// Not using any fancy permissions in android yet...
|
// Not using any fancy permissions in android yet...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlatformHelperAndroid::hideSplashScreen()
|
||||||
|
{
|
||||||
|
// Android's splash will flicker when fading out twice
|
||||||
|
static bool alreadyHiding = false;
|
||||||
|
if (!alreadyHiding) {
|
||||||
|
QtAndroid::hideSplashScreen(250);
|
||||||
|
alreadyHiding = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool PlatformHelperAndroid::hasPermissions() const
|
bool PlatformHelperAndroid::hasPermissions() const
|
||||||
{
|
{
|
||||||
// Not using any fancy permissions in android yet...
|
// Not using any fancy permissions in android yet...
|
||||||
@ -49,10 +59,10 @@ void PlatformHelperAndroid::vibrate(PlatformHelper::HapticsFeedback feedbackType
|
|||||||
int duration;
|
int duration;
|
||||||
switch (feedbackType) {
|
switch (feedbackType) {
|
||||||
case HapticsFeedbackSelection:
|
case HapticsFeedbackSelection:
|
||||||
duration = 15;
|
duration = 20;
|
||||||
break;
|
break;
|
||||||
case HapticsFeedbackImpact:
|
case HapticsFeedbackImpact:
|
||||||
duration = 25;
|
duration = 30;
|
||||||
break;
|
break;
|
||||||
case HapticsFeedbackNotification:
|
case HapticsFeedbackNotification:
|
||||||
duration = 500;
|
duration = 500;
|
||||||
|
|||||||
@ -13,6 +13,8 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void requestPermissions() override;
|
Q_INVOKABLE void requestPermissions() override;
|
||||||
|
|
||||||
|
Q_INVOKABLE void hideSplashScreen() override;
|
||||||
|
|
||||||
bool hasPermissions() const override;
|
bool hasPermissions() const override;
|
||||||
QString machineHostname() const override;
|
QString machineHostname() const override;
|
||||||
QString deviceSerial() const override;
|
QString deviceSerial() const override;
|
||||||
|
|||||||
@ -10,6 +10,11 @@ void PlatformHelperGeneric::requestPermissions()
|
|||||||
emit permissionsRequestFinished();
|
emit permissionsRequestFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlatformHelperGeneric::hideSplashScreen()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool PlatformHelperGeneric::hasPermissions() const
|
bool PlatformHelperGeneric::hasPermissions() const
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -12,6 +12,8 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE virtual void requestPermissions() override;
|
Q_INVOKABLE virtual void requestPermissions() override;
|
||||||
|
|
||||||
|
Q_INVOKABLE virtual void hideSplashScreen() override;
|
||||||
|
|
||||||
virtual bool hasPermissions() const override;
|
virtual bool hasPermissions() const override;
|
||||||
virtual QString machineHostname() const override;
|
virtual QString machineHostname() const override;
|
||||||
virtual QString deviceSerial() const override;
|
virtual QString deviceSerial() const override;
|
||||||
|
|||||||
@ -12,6 +12,11 @@ void PlatformHelperIOS::requestPermissions()
|
|||||||
emit permissionsRequestFinished();
|
emit permissionsRequestFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlatformHelperIOS::hideSplashScreen()
|
||||||
|
{
|
||||||
|
// Nothing to be done
|
||||||
|
}
|
||||||
|
|
||||||
bool PlatformHelperIOS::hasPermissions() const
|
bool PlatformHelperIOS::hasPermissions() const
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -13,6 +13,8 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE virtual void requestPermissions() override;
|
Q_INVOKABLE virtual void requestPermissions() override;
|
||||||
|
|
||||||
|
Q_INVOKABLE void hideSplashScreen() override;
|
||||||
|
|
||||||
virtual bool hasPermissions() const override;
|
virtual bool hasPermissions() const override;
|
||||||
virtual QString machineHostname() const override;
|
virtual QString machineHostname() const override;
|
||||||
virtual QString deviceSerial() const override;
|
virtual QString deviceSerial() const override;
|
||||||
|
|||||||
@ -163,5 +163,6 @@
|
|||||||
<file>ui/thingconfiguration/SetupWizard.qml</file>
|
<file>ui/thingconfiguration/SetupWizard.qml</file>
|
||||||
<file>ui/thingconfiguration/EditThingsPage.qml</file>
|
<file>ui/thingconfiguration/EditThingsPage.qml</file>
|
||||||
<file>ui/thingconfiguration/ConfigureThingPage.qml</file>
|
<file>ui/thingconfiguration/ConfigureThingPage.qml</file>
|
||||||
|
<file>ui/connection/CertificateDialog.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@ -38,7 +38,6 @@ ApplicationWindow {
|
|||||||
property alias windowWidth: app.width
|
property alias windowWidth: app.width
|
||||||
property alias windowHeight: app.height
|
property alias windowHeight: app.height
|
||||||
property bool returnToHome: false
|
property bool returnToHome: false
|
||||||
property bool darkTheme: false
|
|
||||||
property string graphStyle: "bars"
|
property string graphStyle: "bars"
|
||||||
property string style: "light"
|
property string style: "light"
|
||||||
property bool showHiddenOptions: false
|
property bool showHiddenOptions: false
|
||||||
@ -52,6 +51,14 @@ ApplicationWindow {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NymeaDiscovery {
|
||||||
|
id: discovery
|
||||||
|
objectName: "discovery"
|
||||||
|
awsClient: AWSClient
|
||||||
|
// discovering: pageStack.currentItem.objectName === "discoveryPage"
|
||||||
|
}
|
||||||
|
property alias _discovery: discovery
|
||||||
|
|
||||||
onClosing: {
|
onClosing: {
|
||||||
rootItem.handleCloseEvent(close)
|
rootItem.handleCloseEvent(close)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import QtQuick.Layouts 1.3
|
|||||||
import Qt.labs.settings 1.0
|
import Qt.labs.settings 1.0
|
||||||
import Nymea 1.0
|
import Nymea 1.0
|
||||||
import "components"
|
import "components"
|
||||||
|
import "connection"
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@ -35,6 +36,14 @@ Item {
|
|||||||
tabbar.currentIndex = swipeView.currentIndex
|
tabbar.currentIndex = swipeView.currentIndex
|
||||||
}
|
}
|
||||||
function removeTab(index) {
|
function removeTab(index) {
|
||||||
|
if (swipeView.currentIndex === index) {
|
||||||
|
if (swipeView.currentIndex > 0) {
|
||||||
|
swipeView.currentIndex--;
|
||||||
|
} else {
|
||||||
|
swipeView.currentIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
remove(index);
|
remove(index);
|
||||||
settings.tabCount--;
|
settings.tabCount--;
|
||||||
tabbar.currentIndex = swipeView.currentIndex
|
tabbar.currentIndex = swipeView.currentIndex
|
||||||
@ -80,7 +89,7 @@ Item {
|
|||||||
readonly property Engine engine: engineObject
|
readonly property Engine engine: engineObject
|
||||||
readonly property Engine _engine: engineObject // In case a child cannot use "engine"
|
readonly property Engine _engine: engineObject // In case a child cannot use "engine"
|
||||||
property int connectionTabIndex: index
|
property int connectionTabIndex: index
|
||||||
onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.connection.url
|
// onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.connection.url
|
||||||
|
|
||||||
Binding {
|
Binding {
|
||||||
target: AWSClient
|
target: AWSClient
|
||||||
@ -89,19 +98,33 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
pageStack.push(Qt.resolvedUrl("connection/ConnectPage.qml"))
|
if (tabSettings.lastConnectedHost.length > 0) {
|
||||||
setupPushNotifications();
|
print("Last connected host was", tabSettings.lastConnectedHost)
|
||||||
|
var cachedHost = discovery.nymeaHosts.find(tabSettings.lastConnectedHost);
|
||||||
|
if (cachedHost) {
|
||||||
|
engine.connection.connect(cachedHost)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
print("Warning: There is a last connected host but UUID is unknown to discovery...")
|
||||||
|
}
|
||||||
|
PlatformHelper.hideSplashScreen();
|
||||||
|
pageStack.push(Qt.resolvedUrl("connection/ConnectPage.qml"), StackView.Immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer { running: true; repeat: false; interval: 3000; onTriggered: PlatformHelper.hideSplashScreen(); }
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
print("calling init. Auth required:", engine.jsonRpcClient.authenticationRequired, "initial setup required:", engine.jsonRpcClient.initialSetupRequired, "jsonrpc connected:", engine.jsonRpcClient.connected)
|
print("calling init. Auth required:", engine.jsonRpcClient.authenticationRequired, "initial setup required:", engine.jsonRpcClient.initialSetupRequired, "jsonrpc connected:", engine.jsonRpcClient.connected, "Current host:", engine.connection.currentHost)
|
||||||
pageStack.clear()
|
pageStack.clear()
|
||||||
if (!engine.connection.connected) {
|
if (!engine.connection.currentHost) {
|
||||||
|
print("pushing ConnectPage")
|
||||||
pageStack.push(Qt.resolvedUrl("connection/ConnectPage.qml"))
|
pageStack.push(Qt.resolvedUrl("connection/ConnectPage.qml"))
|
||||||
|
PlatformHelper.hideSplashScreen();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (engine.jsonRpcClient.authenticationRequired || engine.jsonRpcClient.initialSetupRequired) {
|
if (engine.jsonRpcClient.authenticationRequired || engine.jsonRpcClient.initialSetupRequired) {
|
||||||
|
PlatformHelper.hideSplashScreen();
|
||||||
if (engine.jsonRpcClient.pushButtonAuthAvailable) {
|
if (engine.jsonRpcClient.pushButtonAuthAvailable) {
|
||||||
print("opening push button auth")
|
print("opening push button auth")
|
||||||
var page = pageStack.push(Qt.resolvedUrl("PushButtonAuthPage.qml"))
|
var page = pageStack.push(Qt.resolvedUrl("PushButtonAuthPage.qml"))
|
||||||
@ -110,6 +133,7 @@ Item {
|
|||||||
engine.connection.disconnect();
|
engine.connection.disconnect();
|
||||||
init();
|
init();
|
||||||
})
|
})
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
var page = pageStack.push(Qt.resolvedUrl("LoginPage.qml"));
|
var page = pageStack.push(Qt.resolvedUrl("LoginPage.qml"));
|
||||||
page.backPressed.connect(function() {
|
page.backPressed.connect(function() {
|
||||||
@ -117,12 +141,21 @@ Item {
|
|||||||
engine.connection.disconnect()
|
engine.connection.disconnect()
|
||||||
init();
|
init();
|
||||||
})
|
})
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else if (engine.jsonRpcClient.connected) {
|
|
||||||
pageStack.push(Qt.resolvedUrl("MainPage.qml"))
|
|
||||||
} else {
|
|
||||||
pageStack.push(Qt.resolvedUrl("connection/ConnectPage.qml"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (engine.jsonRpcClient.connected) {
|
||||||
|
pageStack.push(Qt.resolvedUrl("MainPage.qml"))
|
||||||
|
PlatformHelper.hideSplashScreen();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("pushing ConnectingPage")
|
||||||
|
var page = pageStack.push(Qt.resolvedUrl("connection/ConnectingPage.qml"));
|
||||||
|
page.cancel.connect(function(){
|
||||||
|
engine.connection.disconnect();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCloseEvent(close) {
|
function handleCloseEvent(close) {
|
||||||
@ -135,7 +168,7 @@ Item {
|
|||||||
pageStack.pop();
|
pageStack.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupPushNotifications(askForPermissions) {
|
function setupPushNotifications(askForPermissions) {
|
||||||
if (askForPermissions === undefined) {
|
if (askForPermissions === undefined) {
|
||||||
@ -161,12 +194,27 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: engine.connection
|
||||||
|
onCurrentHostChanged: {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
onVerifyConnectionCertificate: {
|
||||||
|
print("verify cert!")
|
||||||
|
var certDialogComponent = Qt.createComponent(Qt.resolvedUrl("connection/CertificateDialog.qml"));
|
||||||
|
var popup = certDialogComponent.createObject(root, {url: url, issuerInfo: issuerInfo, fingerprint: fingerprint, pem: pem});
|
||||||
|
popup.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: engine.jsonRpcClient
|
target: engine.jsonRpcClient
|
||||||
onConnectedChanged: {
|
onConnectedChanged: {
|
||||||
print("json client connected changed", engine.jsonRpcClient.connected)
|
print("json client connected changed", engine.jsonRpcClient.connected)
|
||||||
if (engine.jsonRpcClient.connected) {
|
if (engine.jsonRpcClient.connected) {
|
||||||
tabSettings.lastConnectedHost = engine.connection.url
|
discovery.cacheHost(engine.connection.currentHost)
|
||||||
|
tabSettings.lastConnectedHost = engine.jsonRpcClient.serverUuid
|
||||||
}
|
}
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
@ -271,15 +319,23 @@ Item {
|
|||||||
id: tabbar
|
id: tabbar
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Material.elevation: 2
|
Material.elevation: 2
|
||||||
|
position: TabBar.Footer
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: mainRepeater.count
|
model: tabModel.count
|
||||||
|
|
||||||
delegate: TabButton {
|
delegate: TabButton {
|
||||||
id: hostTabButton
|
id: hostTabButton
|
||||||
property var engine: mainRepeater.itemAt(index)._engine
|
property var engine: mainRepeater.itemAt(index)._engine
|
||||||
property string serverName: engine.nymeaConfiguration.serverName
|
property string serverName: engine.nymeaConfiguration.serverName
|
||||||
Material.elevation: index
|
Material.elevation: index
|
||||||
|
width: Math.max(150, tabbar.width / tabbar.count)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Material.foreground
|
||||||
|
opacity: 0.06
|
||||||
|
}
|
||||||
|
|
||||||
contentItem: RowLayout {
|
contentItem: RowLayout {
|
||||||
Label {
|
Label {
|
||||||
@ -324,7 +380,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ Page {
|
|||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
elide: Text.ElideMiddle
|
elide: Text.ElideMiddle
|
||||||
text: engine.connection.url
|
text: engine.connection.currentConnection.url
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("Disconnect")
|
text: qsTr("Disconnect")
|
||||||
|
|||||||
@ -86,14 +86,10 @@ Page {
|
|||||||
secondaryIconName: !model.online ? "../images/cloud-error.svg" : ""
|
secondaryIconName: !model.online ? "../images/cloud-error.svg" : ""
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
print("clicked, connected:", engine.connection.connected, model.id)
|
||||||
if (!engine.connection.connected) {
|
if (!engine.connection.connected) {
|
||||||
var page = pageStack.push(Qt.resolvedUrl("../connection/ConnectingPage.qml"))
|
var host = discovery.nymeaHosts.find(model.id)
|
||||||
page.cancel.connect(function() {
|
engine.connection.connect(host);
|
||||||
engine.connection.disconnect()
|
|
||||||
pageStack.pop(root, StackView.Immediate);
|
|
||||||
pageStack.push(discoveryPage)
|
|
||||||
})
|
|
||||||
engine.connection.connect("cloud://" + model.id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
110
nymea-app/ui/connection/CertificateDialog.qml
Normal file
110
nymea-app/ui/connection/CertificateDialog.qml
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import QtQuick 2.9
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
import QtQuick.Controls.Material 2.2
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
import Nymea 1.0
|
||||||
|
import "../components"
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
id: certDialog
|
||||||
|
width: Math.min(parent.width * .9, 400)
|
||||||
|
x: (parent.width - width) / 2
|
||||||
|
y: (parent.height - height) / 2
|
||||||
|
standardButtons: Dialog.Yes | Dialog.No
|
||||||
|
|
||||||
|
property string url
|
||||||
|
property var fingerprint
|
||||||
|
property var issuerInfo
|
||||||
|
property var pem
|
||||||
|
|
||||||
|
readonly property bool hasOldFingerprint: engine.connection.isTrusted(url)
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: certLayout
|
||||||
|
anchors.fill: parent
|
||||||
|
// spacing: app.margins
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: app.margins
|
||||||
|
ColorIcon {
|
||||||
|
Layout.preferredHeight: app.iconSize * 2
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
name: certDialog.hasOldFingerprint ? "../images/lock-broken.svg" : "../images/info.svg"
|
||||||
|
color: certDialog.hasOldFingerprint ? "red" : app.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: titleLabel
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: certDialog.hasOldFingerprint ? qsTr("Warning") : qsTr("Hi there!")
|
||||||
|
color: certDialog.hasOldFingerprint ? "red" : app.accentColor
|
||||||
|
font.pixelSize: app.largeFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: certDialog.hasOldFingerprint ? qsTr("The certificate of this %1 box has changed!").arg(app.systemName) : qsTr("It seems this is the first time you connect to this %1 box.").arg(app.systemName)
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: certDialog.hasOldFingerprint ? qsTr("Did you change the box's configuration? Verify if this information is correct.") : qsTr("This is the box's certificate. Once you trust it, an encrypted connection will be established.")
|
||||||
|
}
|
||||||
|
|
||||||
|
ThinDivider {}
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
implicitHeight: certGridLayout.implicitHeight
|
||||||
|
Flickable {
|
||||||
|
anchors.fill: parent
|
||||||
|
contentHeight: certGridLayout.implicitHeight
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
policy: contentHeight > height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
id: certGridLayout
|
||||||
|
columns: 2
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: certDialog.issuerInfo
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.columnSpan: 2
|
||||||
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||||
|
text: qsTr("Fingerprint: ") + certDialog.fingerprint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ThinDivider {}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: certDialog.hasOldFingerprint ? qsTr("Do you want to connect nevertheless?") : qsTr("Do you want to trust this device?")
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
engine.connection.acceptCertificate(certDialog.url, certDialog.pem)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,24 +8,17 @@ import "../components"
|
|||||||
Page {
|
Page {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property bool haveHosts: discovery.discoveryModel.count > 0
|
readonly property bool haveHosts: hostsProxy.count > 0
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
print("completed connectPage for tab", connectionTabIndex, "last connected host:", tabSettings.lastConnectedHost)
|
print("Ready to connect")
|
||||||
if (tabSettings.lastConnectedHost.length > 0 && engine.connection.connect(tabSettings.lastConnectedHost)) {
|
|
||||||
var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml"))
|
pageStack.push(discoveryPage, StackView.Immediate)
|
||||||
page.cancel.connect(function() {
|
|
||||||
engine.connection.disconnect();
|
|
||||||
pageStack.pop(root, StackView.Immediate);
|
|
||||||
pageStack.push(discoveryPage)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
pageStack.push(discoveryPage)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectToHost(url) {
|
|
||||||
var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml"))
|
function connectToHost(url, noAnimations) {
|
||||||
|
var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml"), noAnimations ? StackView.Immediate : StackView.PushTransition)
|
||||||
page.cancel.connect(function() {
|
page.cancel.connect(function() {
|
||||||
engine.connection.disconnect()
|
engine.connection.disconnect()
|
||||||
pageStack.pop(root, StackView.Immediate);
|
pageStack.pop(root, StackView.Immediate);
|
||||||
@ -34,60 +27,22 @@ Page {
|
|||||||
engine.connection.connect(url)
|
engine.connection.connect(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
NymeaDiscovery {
|
function connectToHost2(host, noAnimations) {
|
||||||
id: discovery
|
var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml"), noAnimations ? StackView.Immediate : StackView.PushTransition)
|
||||||
objectName: "discovery"
|
page.cancel.connect(function() {
|
||||||
awsClient: AWSClient
|
engine.connection.disconnect()
|
||||||
discovering: pageStack.currentItem.objectName === "discoveryPage"
|
pageStack.pop(root, StackView.Immediate);
|
||||||
|
pageStack.push(discoveryPage)
|
||||||
|
})
|
||||||
|
print("Connecting to host", host)
|
||||||
|
engine.connection.connect(host)
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
NymeaHostsFilterModel {
|
||||||
target: engine.connection
|
id: hostsProxy
|
||||||
onVerifyConnectionCertificate: {
|
discovery: _discovery
|
||||||
print("verify cert!")
|
showUnreachableBearers: false
|
||||||
var popup = certDialogComponent.createObject(root, {url: url, issuerInfo: issuerInfo, fingerprint: fingerprint, pem: pem});
|
nymeaConnection: engine.connection
|
||||||
popup.open();
|
|
||||||
}
|
|
||||||
onConnectionError: {
|
|
||||||
var errorMessage;
|
|
||||||
switch (error) {
|
|
||||||
case "ConnectionRefusedError":
|
|
||||||
errorMessage = qsTr("The host has rejected our connection. This probably means that %1 stopped running. Did you unplug your %1 box?").arg(app.systemName);
|
|
||||||
break;
|
|
||||||
case "SslInvalidUserDataError":
|
|
||||||
case "SslHandshakeFailedError":
|
|
||||||
// silently ignore. They'll be handled by the SSL logic
|
|
||||||
return;
|
|
||||||
case "HostNotFoundError":
|
|
||||||
errorMessage = qsTr("%1:core could not be found on this address. Please make sure you entered the address correctly and that the box is powered on.").arg(app.systemName);
|
|
||||||
break;
|
|
||||||
case "NetworkError":
|
|
||||||
errorMessage = qsTr("It seems you're not connected to the network.");
|
|
||||||
break;
|
|
||||||
case "RemoteHostClosedError":
|
|
||||||
errorMessage = qsTr("%1:core has closed the connection. This probably means it has been turned off or restarted.").arg(app.systemName);
|
|
||||||
break;
|
|
||||||
case "SocketTimeoutError":
|
|
||||||
errorMessage = qsTr("%1:core did not respond. Please make sure your network connection works properly").arg(app.systemName);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
errorMessage = qsTr("An unknown error happened. We're very sorry for that. (Error code: %1)").arg(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
pageStack.pop(root, StackView.Immediate)
|
|
||||||
pageStack.push(discoveryPage)
|
|
||||||
|
|
||||||
print("opening ErrorDialog with message:", errorMessage, error)
|
|
||||||
var comp = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml"))
|
|
||||||
var popup = comp.createObject(app, {text: errorMessage})
|
|
||||||
popup.open()
|
|
||||||
}
|
|
||||||
onConnectedChanged: {
|
|
||||||
if (!connected) {
|
|
||||||
pageStack.pop(root, StackView.Immediate)
|
|
||||||
pageStack.push(discoveryPage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
@ -105,7 +60,8 @@ Page {
|
|||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (index === 2) {
|
if (index === 2) {
|
||||||
root.connectToHost("nymea://nymea.nymea.io:2222")
|
var host = discovery.nymeaHosts.createHost("Demo server", "nymea://nymea.nymea.io:2222", Connection.BearerTypeCloud)
|
||||||
|
engine.connection.connect(host)
|
||||||
} else {
|
} else {
|
||||||
pageStack.push(model.get(index).page, {nymeaDiscovery: discovery});
|
pageStack.push(model.get(index).page, {nymeaDiscovery: discovery});
|
||||||
}
|
}
|
||||||
@ -113,10 +69,21 @@ Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: startupTimer
|
id: splashHideTimeout
|
||||||
interval: 5000
|
interval: 3000
|
||||||
repeat: false
|
repeat: false
|
||||||
running: true
|
running: true
|
||||||
|
onTriggered: {
|
||||||
|
PlatformHelper.hideSplashScreen()
|
||||||
|
startupTimer.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: startupTimer
|
||||||
|
interval: 10000
|
||||||
|
repeat: false
|
||||||
|
running: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -141,7 +108,7 @@ Page {
|
|||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: root.haveHosts ?
|
text: root.haveHosts ?
|
||||||
qsTr("There are %1 %2 boxes in your network! Which one would you like to use?").arg(discovery.discoveryModel.count).arg(app.systemName)
|
qsTr("There are %1 %2 boxes in your network! Which one would you like to use?").arg(discovery.nymeaHosts.count).arg(app.systemName)
|
||||||
: startupTimer.running ? qsTr("We haven't found any %1 boxes in your network yet.").arg(app.systemName)
|
: startupTimer.running ? qsTr("We haven't found any %1 boxes in your network yet.").arg(app.systemName)
|
||||||
: qsTr("There doesn't seem to be a %1 box installed in your network. Please make sure your %1 box is correctly set up and connected.").arg(app.systemName)
|
: qsTr("There doesn't seem to be a %1 box installed in your network. Please make sure your %1 box is correctly set up and connected.").arg(app.systemName)
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
@ -153,53 +120,30 @@ Page {
|
|||||||
ListView {
|
ListView {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
model: discovery.discoveryModel
|
model: hostsProxy
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
delegate: MeaListItemDelegate {
|
delegate: MeaListItemDelegate {
|
||||||
id: discoveryDeviceDelegate
|
id: nymeaHostDelegate
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: app.delegateHeight
|
height: app.delegateHeight
|
||||||
objectName: "discoveryDelegate" + index
|
objectName: "discoveryDelegate" + index
|
||||||
property var discoveryDevice: discovery.discoveryModel.get(index)
|
property var nymeaHost: hostsProxy.get(index)
|
||||||
property string defaultConnectionIndex: {
|
property string defaultConnectionIndex: {
|
||||||
var usedConfigIndex = 0;
|
var bestIndex = -1
|
||||||
for (var i = 1; i < discoveryDevice.connections.count; i++) {
|
var bestPriority = 0;
|
||||||
var oldConfig = discoveryDevice.connections.get(usedConfigIndex);
|
for (var i = 0; i < nymeaHost.connections.count; i++) {
|
||||||
var newConfig = discoveryDevice.connections.get(i);
|
var connection = nymeaHost.connections.get(i);
|
||||||
|
if (bestIndex === -1 || connection.priority > bestPriority) {
|
||||||
// Preference of bearerType
|
bestIndex = i;
|
||||||
var bearerPreference = [Connection.BearerTypeEthernet, Connection.BearerTypeWifi, Connection.BearerTypeBluetooth, Connection.BearerTypeCloud]
|
bestPriority = connection.priority;
|
||||||
var oldBearerPriority = bearerPreference.indexOf(oldConfig.bearerType);
|
|
||||||
var newBearerPriority = bearerPreference.indexOf(newConfig.bearerType);
|
|
||||||
if (newBearerPriority < oldBearerPriority) {
|
|
||||||
print(discoveryDevice.name, "switching to preferred index", i, "of bearer type", newConfig.bearerType, "from", oldConfig.bearerType, "new prio:", newBearerPriority, "old:", oldBearerPriority)
|
|
||||||
usedConfigIndex = i;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (oldBearerPriority < newBearerPriority) {
|
|
||||||
continue; // discard new one the one we have is on a better bearer type
|
|
||||||
}
|
|
||||||
|
|
||||||
// prefer secure over insecure
|
|
||||||
if (!oldConfig.secure && newConfig.secure) {
|
|
||||||
usedConfigIndex = i;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (oldConfig.secure && !newConfig.secure) {
|
|
||||||
continue; // discard new one as the one we already have is more secure
|
|
||||||
}
|
|
||||||
|
|
||||||
// both options are now on the same bearer and either secure or insecure, prefer nymearpc over websocket for less overhead
|
|
||||||
if (oldConfig.url.toString().startsWith("ws") && newConfig.url.toString().startsWith("nymea")) {
|
|
||||||
usedConfigIndex = i;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return usedConfigIndex
|
return bestIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
iconName: {
|
iconName: {
|
||||||
switch (discoveryDevice.connections.get(defaultConnectionIndex).bearerType) {
|
switch (nymeaHost.connections.get(defaultConnectionIndex).bearerType) {
|
||||||
case Connection.BearerTypeWifi:
|
case Connection.BearerTypeWifi:
|
||||||
return "../images/network-wifi-symbolic.svg";
|
return "../images/network-wifi-symbolic.svg";
|
||||||
case Connection.BearerTypeEthernet:
|
case Connection.BearerTypeEthernet:
|
||||||
@ -213,21 +157,21 @@ Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
text: model.name
|
text: model.name
|
||||||
subText: discoveryDevice.connections.get(defaultConnectionIndex).url
|
subText: nymeaHost.connections.get(defaultConnectionIndex).url
|
||||||
wrapTexts: false
|
wrapTexts: false
|
||||||
prominentSubText: false
|
prominentSubText: false
|
||||||
progressive: false
|
progressive: false
|
||||||
property bool isSecure: discoveryDevice.connections.get(defaultConnectionIndex).secure
|
property bool isSecure: nymeaHost.connections.get(defaultConnectionIndex).secure
|
||||||
property bool isTrusted: engine.connection.isTrusted(discoveryDeviceDelegate.discoveryDevice.connections.get(defaultConnectionIndex).url)
|
property bool isTrusted: engine.connection.isTrusted(nymeaHostDelegate.nymeaHost.connections.get(defaultConnectionIndex).url)
|
||||||
property bool isOnline: discoveryDevice.connections.get(defaultConnectionIndex).online
|
property bool isOnline: nymeaHost.connections.get(defaultConnectionIndex).online
|
||||||
tertiaryIconName: isSecure ? "../images/network-secure.svg" : ""
|
tertiaryIconName: isSecure ? "../images/network-secure.svg" : ""
|
||||||
tertiaryIconColor: isTrusted ? app.accentColor : Material.foreground
|
tertiaryIconColor: isTrusted ? app.accentColor : Material.foreground
|
||||||
secondaryIconName: !isOnline ? "../images/cloud-error.svg" : ""
|
secondaryIconName: !isOnline ? "../images/cloud-error.svg" : ""
|
||||||
secondaryIconColor: "red"
|
secondaryIconColor: "red"
|
||||||
swipe.enabled: discoveryDeviceDelegate.discoveryDevice.deviceType === DiscoveryDevice.DeviceTypeNetwork
|
swipe.enabled: nymeaHostDelegate.nymeaHost.deviceType === NymeaHost.DeviceTypeNetwork
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.connectToHost(discoveryDeviceDelegate.discoveryDevice.connections.get(defaultConnectionIndex).url)
|
root.connectToHost2(nymeaHostDelegate.nymeaHost)
|
||||||
}
|
}
|
||||||
|
|
||||||
swipe.right: MouseArea {
|
swipe.right: MouseArea {
|
||||||
@ -240,9 +184,11 @@ Page {
|
|||||||
name: "../images/info.svg"
|
name: "../images/info.svg"
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (model.deviceType === DiscoveryDevice.DeviceTypeNetwork) {
|
if (model.deviceType === NymeaHost.DeviceTypeNetwork) {
|
||||||
swipe.close()
|
swipe.close()
|
||||||
var popup = infoDialog.createObject(app,{discoveryDevice: discovery.discoveryModel.get(index)})
|
var nymeaHost = hostsProxy.get(index);
|
||||||
|
print("Getting info for", nymeaHost.name)
|
||||||
|
var popup = infoDialog.createObject(app,{nymeaHost: nymeaHost})
|
||||||
popup.open()
|
popup.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,14 +218,14 @@ Page {
|
|||||||
Layout.leftMargin: app.margins
|
Layout.leftMargin: app.margins
|
||||||
Layout.rightMargin: app.margins
|
Layout.rightMargin: app.margins
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
visible: discovery.discoveryModel.count === 0
|
visible: discovery.nymeaHosts.count === 0
|
||||||
text: qsTr("Do you have a %1 box but it's not connected to your network yet? Use the wireless setup to connect it!").arg(app.systemName)
|
text: qsTr("Do you have a %1 box but it's not connected to your network yet? Use the wireless setup to connect it!").arg(app.systemName)
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: app.margins
|
Layout.leftMargin: app.margins
|
||||||
Layout.rightMargin: app.margins
|
Layout.rightMargin: app.margins
|
||||||
visible: discovery.discoveryModel.count === 0
|
visible: discovery.nymeaHosts.count === 0
|
||||||
text: qsTr("Start wireless setup")
|
text: qsTr("Start wireless setup")
|
||||||
onClicked: pageStack.push(Qt.resolvedUrl("wifisetup/BluetoothDiscoveryPage.qml"), {nymeaDiscovery: discovery})
|
onClicked: pageStack.push(Qt.resolvedUrl("wifisetup/BluetoothDiscoveryPage.qml"), {nymeaDiscovery: discovery})
|
||||||
}
|
}
|
||||||
@ -297,10 +243,11 @@ Page {
|
|||||||
Layout.leftMargin: app.margins
|
Layout.leftMargin: app.margins
|
||||||
Layout.rightMargin: app.margins
|
Layout.rightMargin: app.margins
|
||||||
Layout.bottomMargin: app.margins
|
Layout.bottomMargin: app.margins
|
||||||
visible: discovery.discoveryModel.count === 0
|
visible: discovery.nymeaHosts.count === 0
|
||||||
text: qsTr("Demo mode (online)")
|
text: qsTr("Demo mode (online)")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.connectToHost("nymea://nymea.nymea.io:2222")
|
var host = nymeaHosts.createHost("Demo server", "nymea://nymea.nymea.io:2222", Connection.BearerTypeCloud)
|
||||||
|
engine.connection.connect(host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,116 +269,6 @@ Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
|
||||||
id: certDialogComponent
|
|
||||||
|
|
||||||
Dialog {
|
|
||||||
id: certDialog
|
|
||||||
width: Math.min(parent.width * .9, 400)
|
|
||||||
x: (parent.width - width) / 2
|
|
||||||
y: (parent.height - height) / 2
|
|
||||||
standardButtons: Dialog.Yes | Dialog.No
|
|
||||||
|
|
||||||
property string url
|
|
||||||
property var fingerprint
|
|
||||||
property var issuerInfo
|
|
||||||
property var pem
|
|
||||||
|
|
||||||
readonly property bool hasOldFingerprint: engine.connection.isTrusted(url)
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: certLayout
|
|
||||||
anchors.fill: parent
|
|
||||||
// spacing: app.margins
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: app.margins
|
|
||||||
ColorIcon {
|
|
||||||
Layout.preferredHeight: app.iconSize * 2
|
|
||||||
Layout.preferredWidth: height
|
|
||||||
name: certDialog.hasOldFingerprint ? "../images/lock-broken.svg" : "../images/info.svg"
|
|
||||||
color: certDialog.hasOldFingerprint ? "red" : app.accentColor
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: titleLabel
|
|
||||||
Layout.fillWidth: true
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
text: certDialog.hasOldFingerprint ? qsTr("Warning") : qsTr("Hi there!")
|
|
||||||
color: certDialog.hasOldFingerprint ? "red" : app.accentColor
|
|
||||||
font.pixelSize: app.largeFont
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
text: certDialog.hasOldFingerprint ? qsTr("The certificate of this %1 box has changed!").arg(app.systemName) : qsTr("It seems this is the first time you connect to this %1 box.").arg(app.systemName)
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
text: certDialog.hasOldFingerprint ? qsTr("Did you change the box's configuration? Verify if this information is correct.") : qsTr("This is the box's certificate. Once you trust it, an encrypted connection will be established.")
|
|
||||||
}
|
|
||||||
|
|
||||||
ThinDivider {}
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
implicitHeight: certGridLayout.implicitHeight
|
|
||||||
Flickable {
|
|
||||||
anchors.fill: parent
|
|
||||||
contentHeight: certGridLayout.implicitHeight
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
|
||||||
policy: contentHeight > height ? ScrollBar.AlwaysOn : ScrollBar.AsNeeded
|
|
||||||
}
|
|
||||||
|
|
||||||
GridLayout {
|
|
||||||
id: certGridLayout
|
|
||||||
columns: 2
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: certDialog.issuerInfo
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
text: modelData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.columnSpan: 2
|
|
||||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
|
||||||
text: qsTr("Fingerprint: ") + certDialog.fingerprint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ThinDivider {}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
text: certDialog.hasOldFingerprint ? qsTr("Do you want to connect nevertheless?") : qsTr("Do you want to trust this device?")
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onAccepted: {
|
|
||||||
engine.connection.acceptCertificate(certDialog.url, certDialog.pem)
|
|
||||||
root.connectToHost(certDialog.url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: infoDialog
|
id: infoDialog
|
||||||
Dialog {
|
Dialog {
|
||||||
@ -444,7 +281,7 @@ Page {
|
|||||||
|
|
||||||
standardButtons: Dialog.Ok
|
standardButtons: Dialog.Ok
|
||||||
|
|
||||||
property var discoveryDevice: null
|
property var nymeaHost: null
|
||||||
|
|
||||||
header: Item {
|
header: Item {
|
||||||
implicitHeight: headerRow.height + app.margins * 2
|
implicitHeight: headerRow.height + app.margins * 2
|
||||||
@ -480,7 +317,7 @@ Page {
|
|||||||
text: "Name:"
|
text: "Name:"
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: dialog.discoveryDevice.name
|
text: dialog.nymeaHost.name
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
@ -488,7 +325,7 @@ Page {
|
|||||||
text: "UUID:"
|
text: "UUID:"
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: dialog.discoveryDevice.uuid
|
text: dialog.nymeaHost.uuid
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
@ -496,7 +333,7 @@ Page {
|
|||||||
text: "Version:"
|
text: "Version:"
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: dialog.discoveryDevice.version
|
text: dialog.nymeaHost.version
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
@ -516,7 +353,7 @@ Page {
|
|||||||
id: contentColumn
|
id: contentColumn
|
||||||
width: parent.width
|
width: parent.width
|
||||||
Repeater {
|
Repeater {
|
||||||
model: dialog.discoveryDevice.connections
|
model: dialog.nymeaHost.connections
|
||||||
delegate: MeaListItemDelegate {
|
delegate: MeaListItemDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
wrapTexts: false
|
wrapTexts: false
|
||||||
@ -545,8 +382,8 @@ Page {
|
|||||||
secondaryIconColor: "red"
|
secondaryIconColor: "red"
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.connectToHost(dialog.discoveryDevice.connections.get(index).url)
|
|
||||||
dialog.close()
|
dialog.close()
|
||||||
|
engine.connection.connect(dialog.nymeaHost, dialog.nymeaHost.connections.get(index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,52 @@ Page {
|
|||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: engine.connection.url
|
text: engine.connection.currentHost.uuid
|
||||||
|
font.pixelSize: app.smallFont
|
||||||
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: {
|
||||||
|
var errorMessage;
|
||||||
|
switch (engine.connection.connectionStatus) {
|
||||||
|
case NymeaConnection.ConnectionStatusUnconnected:
|
||||||
|
case NymeaConnection.ConnectionStatusConnecting:
|
||||||
|
case NymeaConnection.ConnectionStatusConnected:
|
||||||
|
errorMessage = "";
|
||||||
|
break;
|
||||||
|
case NymeaConnection.ConnectionStatusBearerFailed:
|
||||||
|
errorMessage = qsTr("The network connection failed.")
|
||||||
|
break;
|
||||||
|
case NymeaConnection.ConnectionStatusNoBearerAvailable:
|
||||||
|
errorMessage = qsTr("It seems you're not connected to the network.");
|
||||||
|
break;
|
||||||
|
case NymeaConnection.ConnectionStatusHostNotFound:
|
||||||
|
errorMessage = qsTr("%1:core could not be found on this address. Please make sure you entered the address correctly and that the box is powered on.").arg(app.systemName);
|
||||||
|
break;
|
||||||
|
case NymeaConnection.ConnectionStatusConnectionRefused:
|
||||||
|
errorMessage = qsTr("The host has rejected our connection. This probably means that %1 is not running on this host. Perhaps it's restarting?").arg(app.systemName);
|
||||||
|
break;
|
||||||
|
case NymeaConnection.ConnectionStatusRemoteHostClosed:
|
||||||
|
errorMessage = qsTr("%1:core has closed the connection. This probably means it has been turned off or restarted.").arg(app.systemName);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NymeaConnection.ConnectionStatusTimeout:
|
||||||
|
errorMessage = qsTr("%1:core did not respond. Please make sure your network connection works properly").arg(app.systemName);
|
||||||
|
break;
|
||||||
|
case NymeaConnection.ConnectionStatusSslError:
|
||||||
|
errorMessage = qsTr("An unrecovareable SSL Error happened. Please make sure certificates are installed correctly.");
|
||||||
|
break;
|
||||||
|
case NymeaConnection.ConnectionStatusSslUntrusted:
|
||||||
|
errorMessage = qsTr("The SSL Certificate is not trusted.");
|
||||||
|
break;
|
||||||
|
case NymeaConnection.ConnectionStatusUnknownError:
|
||||||
|
default:
|
||||||
|
errorMessage = qsTr("An unknown error happened. We're very sorry for that.").arg(engine.connection.connectionStatus);
|
||||||
|
}
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
font.pixelSize: app.smallFont
|
font.pixelSize: app.smallFont
|
||||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
|||||||
@ -96,12 +96,8 @@ Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
print("Try to connect ", rpcUrl)
|
print("Try to connect ", rpcUrl)
|
||||||
engine.connection.connect(rpcUrl)
|
var host = discovery.nymeaHosts.createHost("Manual connection", rpcUrl, Connection.BearerTypeCloud);
|
||||||
var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml"))
|
engine.connection.connect(host)
|
||||||
page.cancel.connect(function() {
|
|
||||||
engine.connection.disconnect()
|
|
||||||
pageStack.pop(root)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,7 @@ Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.nymeaDiscovery.discoveryModel
|
target: root.nymeadiscovery.nymeaHosts
|
||||||
onCountChanged: updateConnectButton();
|
onCountChanged: updateConnectButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,14 +63,14 @@ Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: We should rather look for the UUID here, but nymea-networkmanager doesn't support getting us the nymea uuid (yet)
|
// FIXME: We should rather look for the UUID here, but nymea-networkmanager doesn't support getting us the nymea uuid (yet)
|
||||||
for (var i = 0; i < root.nymeaDiscovery.discoveryModel.count; i++) {
|
for (var i = 0; i < root.nymeadiscovery.nymeaHosts.count; i++) {
|
||||||
for (var j = 0; j < root.nymeaDiscovery.discoveryModel.get(i).connections.count; j++) {
|
for (var j = 0; j < root.nymeadiscovery.nymeaHosts.get(i).connections.count; j++) {
|
||||||
if (root.nymeaDiscovery.discoveryModel.get(i).connections.get(j).url.toString().indexOf(root.networkManagerController.manager.currentConnection.hostAddress) >= 0) {
|
if (root.nymeadiscovery.nymeaHosts.get(i).connections.get(j).url.toString().indexOf(root.networkManagerController.manager.currentConnection.hostAddress) >= 0) {
|
||||||
connectButton.url = root.nymeaDiscovery.discoveryModel.get(i).connections.get(j).url
|
connectButton.url = root.nymeadiscovery.nymeaHosts.get(i).connections.get(j).url
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
root.nymeaDiscovery.discoveryModel.get(i).connections.countChanged.connect(function() {
|
root.nymeadiscovery.nymeaHosts.get(i).connections.countChanged.connect(function() {
|
||||||
updateConnectButton();
|
updateConnectButton();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -185,7 +185,7 @@ Item {
|
|||||||
readonly property var powerState: device.states.getState(powerStateType.id)
|
readonly property var powerState: device.states.getState(powerStateType.id)
|
||||||
|
|
||||||
readonly property var brightnessStateType: deviceClass.stateTypes.findByName("brightness");
|
readonly property var brightnessStateType: deviceClass.stateTypes.findByName("brightness");
|
||||||
readonly property var brightnessState: device.states.getState(brightnessStateType.id)
|
readonly property var brightnessState: brightnessStateType ? device.states.getState(brightnessStateType.id) : null
|
||||||
|
|
||||||
ThrottledSlider {
|
ThrottledSlider {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@ -195,7 +195,7 @@ Item {
|
|||||||
enabled: opacity > 0
|
enabled: opacity > 0
|
||||||
from: 0
|
from: 0
|
||||||
to: 100
|
to: 100
|
||||||
value: brightnessState.value
|
value: brightnessState ? brightnessState.value : 0
|
||||||
onMoved: {
|
onMoved: {
|
||||||
var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
|
var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
|
||||||
var actionType = deviceClass.actionTypes.findByName("brightness");
|
var actionType = deviceClass.actionTypes.findByName("brightness");
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<manifest package="io.guh.nymeaapp" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
|
<manifest package="io.guh.nymeaapp" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
|
||||||
|
|
||||||
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="nymea:app" android:icon="@mipmap/icon" android:roundIcon="@mipmap/round_icon">
|
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="nymea:app" android:icon="@mipmap/icon" android:roundIcon="@mipmap/round_icon">
|
||||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="io.guh.nymeaapp.NymeaAppActivity" android:label="nymea:app" android:screenOrientation="unspecified" android:launchMode="singleTop">
|
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="io.guh.nymeaapp.NymeaAppActivity" android:label="nymea:app" android:screenOrientation="unspecified" android:launchMode="singleTop" android:theme="@style/SplashScreenTheme">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|||||||
BIN
packaging/android/res/drawable-mdpi/icon.png
Normal file
BIN
packaging/android/res/drawable-mdpi/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
packaging/android/res/drawable-mdpi/round_icon.png
Normal file
BIN
packaging/android/res/drawable-mdpi/round_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@ -2,11 +2,11 @@
|
|||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item>
|
<item>
|
||||||
<shape android:shape="rectangle" >
|
<shape android:shape="rectangle" >
|
||||||
<solid android:color="#FFFFFFFF"/>
|
<solid android:color="#303030"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<bitmap android:src="@mipmap/icon"
|
<bitmap android:src="@drawable/round_icon"
|
||||||
android:gravity="center" />
|
android:gravity="center" />
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
6
packaging/android/res/values/splashscreentheme.xml
Normal file
6
packaging/android/res/values/splashscreentheme.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="SplashScreenTheme">
|
||||||
|
<item name="android:windowBackground">@drawable/splash</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
Reference in New Issue
Block a user