diff --git a/libnymea-app-core/connection/awsclient.cpp b/libnymea-app-core/connection/awsclient.cpp index e89dbc29..39268c80 100644 --- a/libnymea-app-core/connection/awsclient.cpp +++ b/libnymea-app-core/connection/awsclient.cpp @@ -866,25 +866,25 @@ bool AWSClient::postToMQTT(const QString &boxId, const QString ×tamp, std:: request.setUrl("https://" + m_configs.value(m_usedConfig).mqttEndpoint + path1); qDebug() << "Posting to MQTT:" << request.url().toString(); - qDebug() << "HEADERS:"; - foreach (const QByteArray &headerName, request.rawHeaderList()) { - qDebug() << headerName << ":" << request.rawHeader(headerName); - } - qDebug() << "Payload:" << payload; +// qDebug() << "HEADERS:"; +// foreach (const QByteArray &headerName, request.rawHeaderList()) { +// qDebug() << headerName << ":" << request.rawHeader(headerName); +// } +// qDebug() << "Payload:" << payload; QNetworkReply *reply = m_nam->post(request, payload); connect(reply, &QNetworkReply::finished, this, [reply, callback]() { reply->deleteLater(); QByteArray data = reply->readAll(); - qDebug() << "post reply" << data; +// qDebug() << "MQTT post reply" << data; if (reply->error() != QNetworkReply::NoError) { - qWarning() << "Network reply error" << reply->error() << reply->errorString(); + qWarning() << "MQTT Network reply error" << reply->error() << reply->errorString(); callback(false); return; } QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); 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); return; } diff --git a/libnymea-app-core/connection/bluetoothtransport.cpp b/libnymea-app-core/connection/bluetoothtransport.cpp index af12a988..68e9969b 100644 --- a/libnymea-app-core/connection/bluetoothtransport.cpp +++ b/libnymea-app-core/connection/bluetoothtransport.cpp @@ -43,6 +43,7 @@ bool BluetoothTransport::connect(const QUrl &url) qWarning() << "BluetoothInterface: Cannot connect. Invalid scheme in url" << url.toString(); return false; } + m_url = url; QUrlQuery query(url); QString macAddressString = query.queryItemValue("mac"); @@ -54,6 +55,11 @@ bool BluetoothTransport::connect(const QUrl &url) return true; } +QUrl BluetoothTransport::url() const +{ + return m_url; +} + void BluetoothTransport::disconnect() { m_socket->close(); diff --git a/libnymea-app-core/connection/bluetoothtransport.h b/libnymea-app-core/connection/bluetoothtransport.h index 6eb3ddc3..32a032ea 100644 --- a/libnymea-app-core/connection/bluetoothtransport.h +++ b/libnymea-app-core/connection/bluetoothtransport.h @@ -24,6 +24,7 @@ #define BLUETOOTHTRANSPORT_H #include +#include #include #include "nymeatransportinterface.h" @@ -42,11 +43,13 @@ public: explicit BluetoothTransport(QObject *parent = nullptr); bool connect(const QUrl &url) override; + QUrl url() const override; void disconnect() override; ConnectionState connectionState() const override; void sendData(const QByteArray &data) override; private: + QUrl m_url; QBluetoothSocket *m_socket = nullptr; QBluetoothServiceInfo m_service; diff --git a/libnymea-app-core/connection/cloudtransport.cpp b/libnymea-app-core/connection/cloudtransport.cpp index e9575792..03818ad8 100644 --- a/libnymea-app-core/connection/cloudtransport.cpp +++ b/libnymea-app-core/connection/cloudtransport.cpp @@ -49,6 +49,7 @@ bool CloudTransport::connect(const QUrl &url) } qDebug() << "Connecting to" << url; + m_url = url; m_timestamp = QDateTime::currentDateTime(); bool postResult = m_awsClient->postToMQTT(url.host(), QString::number(m_timestamp.toMSecsSinceEpoch()), [this](bool success) { @@ -68,6 +69,11 @@ bool CloudTransport::connect(const QUrl &url) return true; } +QUrl CloudTransport::url() const +{ + return m_url; +} + void CloudTransport::disconnect() { qDebug() << "CloudTransport: Disconnecting from server."; diff --git a/libnymea-app-core/connection/cloudtransport.h b/libnymea-app-core/connection/cloudtransport.h index 90ecb445..00d09ea9 100644 --- a/libnymea-app-core/connection/cloudtransport.h +++ b/libnymea-app-core/connection/cloudtransport.h @@ -4,6 +4,7 @@ #include "nymeatransportinterface.h" #include +#include class AWSClient; namespace remoteproxyclient { @@ -25,12 +26,14 @@ public: explicit CloudTransport(AWSClient *awsClient, QObject *parent = nullptr); bool connect(const QUrl &url) override; + QUrl url() const override; void disconnect() override; ConnectionState connectionState() const override; void sendData(const QByteArray &data) override; void ignoreSslErrors(const QList &errors) override; private: + QUrl m_url; AWSClient *m_awsClient = nullptr; remoteproxyclient::RemoteProxyConnection *m_remoteproxyConnection = nullptr; QDateTime m_timestamp; diff --git a/libnymea-app-core/discovery/bluetoothservicediscovery.cpp b/libnymea-app-core/connection/discovery/bluetoothservicediscovery.cpp similarity index 87% rename from libnymea-app-core/discovery/bluetoothservicediscovery.cpp rename to libnymea-app-core/connection/discovery/bluetoothservicediscovery.cpp index 39ebbfc2..9485a9f5 100644 --- a/libnymea-app-core/discovery/bluetoothservicediscovery.cpp +++ b/libnymea-app-core/connection/discovery/bluetoothservicediscovery.cpp @@ -1,13 +1,13 @@ #include "bluetoothservicediscovery.h" -#include "discoverymodel.h" -#include "discoverydevice.h" +#include "../nymeahosts.h" +#include "../nymeahost.h" #include -BluetoothServiceDiscovery::BluetoothServiceDiscovery(DiscoveryModel *discoveryModel, QObject *parent) : +BluetoothServiceDiscovery::BluetoothServiceDiscovery(NymeaHosts *nymeaHosts, QObject *parent) : QObject(parent), - m_discoveryModel(discoveryModel) + m_nymeaHosts(nymeaHosts) { m_nymeaServiceUuid = QBluetoothUuid(QUuid("997936b5-d2cd-4c57-b41b-c6048320cd2b")); @@ -29,7 +29,7 @@ bool BluetoothServiceDiscovery::available() const if (!m_localDevice) return false; - return m_localDevice->isValid() && !m_localDevice->hostMode() != QBluetoothLocalDevice::HostPoweredOff; + return m_localDevice->isValid() && m_localDevice->hostMode() != QBluetoothLocalDevice::HostPoweredOff; } void BluetoothServiceDiscovery::discover() @@ -101,15 +101,15 @@ void BluetoothServiceDiscovery::onServiceDiscovered(const QBluetoothServiceInfo if (serviceInfo.serviceClassUuids().first() == QBluetoothUuid(QUuid("997936b5-d2cd-4c57-b41b-c6048320cd2b"))) { qDebug() << "BluetoothServiceDiscovery: Found nymea rfcom service!"; -// DiscoveryDevice* device = m_discoveryModel->find(serviceInfo.device().address()); -// if (!device) { -// device = new DiscoveryDevice(DiscoveryDevice::DeviceTypeBluetooth, this); +// NymeaHost* host = m_nymeaHosts->find(serviceInfo.device().address()); +// if (!host) { +// host = new DiscoveryDevice(DiscoveryDevice::DeviceTypeBluetooth, this); // 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()); // PortConfig pc; -// m_discoveryModel->addDevice(device); +// m_nymeaHosts->addHost(device); // } } } diff --git a/libnymea-app-core/discovery/bluetoothservicediscovery.h b/libnymea-app-core/connection/discovery/bluetoothservicediscovery.h similarity index 85% rename from libnymea-app-core/discovery/bluetoothservicediscovery.h rename to libnymea-app-core/connection/discovery/bluetoothservicediscovery.h index 1da95907..40c18737 100644 --- a/libnymea-app-core/discovery/bluetoothservicediscovery.h +++ b/libnymea-app-core/connection/discovery/bluetoothservicediscovery.h @@ -6,13 +6,13 @@ #include #include -class DiscoveryModel; +class NymeaHosts; class BluetoothServiceDiscovery : public QObject { Q_OBJECT public: - explicit BluetoothServiceDiscovery(DiscoveryModel *discoveryModel, QObject *parent = nullptr); + explicit BluetoothServiceDiscovery(NymeaHosts *nymeaHosts, QObject *parent = nullptr); bool discovering() const; bool available() const; @@ -21,7 +21,7 @@ public: Q_INVOKABLE void stopDiscovery(); private: - DiscoveryModel *m_discoveryModel = nullptr; + NymeaHosts *m_nymeaHosts = nullptr; QBluetoothLocalDevice *m_localDevice = nullptr; QBluetoothServiceDiscoveryAgent *m_serviceDiscovery = nullptr; QBluetoothUuid m_nymeaServiceUuid; diff --git a/libnymea-app-core/connection/discovery/nymeadiscovery.cpp b/libnymea-app-core/connection/discovery/nymeadiscovery.cpp new file mode 100644 index 00000000..5dc0566f --- /dev/null +++ b/libnymea-app-core/connection/discovery/nymeadiscovery.cpp @@ -0,0 +1,227 @@ +#include "nymeadiscovery.h" +#include "upnpdiscovery.h" +#include "zeroconfdiscovery.h" +#include "bluetoothservicediscovery.h" +#include "connection/awsclient.h" +#include "../nymeahost.h" + +#include +#include +#include +#include +#include +#include + +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 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 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(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() +{ +} + diff --git a/libnymea-app-core/discovery/nymeadiscovery.h b/libnymea-app-core/connection/discovery/nymeadiscovery.h similarity index 78% rename from libnymea-app-core/discovery/nymeadiscovery.h rename to libnymea-app-core/connection/discovery/nymeadiscovery.h index 46aaabac..b486b2b7 100644 --- a/libnymea-app-core/discovery/nymeadiscovery.h +++ b/libnymea-app-core/connection/discovery/nymeadiscovery.h @@ -6,8 +6,9 @@ #include #include "connection/awsclient.h" +#include "connection/nymeahost.h" -class DiscoveryModel; +class NymeaHosts; class UpnpDiscovery; class ZeroconfDiscovery; class BluetoothServiceDiscovery; @@ -17,22 +18,23 @@ class NymeaDiscovery : public QObject { Q_OBJECT 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(NymeaHosts* nymeaHosts READ nymeaHosts CONSTANT) + public: explicit NymeaDiscovery(QObject *parent = nullptr); + ~NymeaDiscovery(); bool discovering() const; void setDiscovering(bool discovering); - DiscoveryModel *discoveryModel() const; + NymeaHosts *nymeaHosts() const; AWSClient* awsClient() const; void setAwsClient(AWSClient *awsClient); - Q_INVOKABLE void resolveServerUuid(const QUuid &uuid); + Q_INVOKABLE void cacheHost(NymeaHost* host); signals: void discoveringChanged(); @@ -43,14 +45,19 @@ signals: private slots: void syncCloudDevices(); + void loadFromDisk(); + + void updateActiveBearers(); + private: bool m_discovering = false; - DiscoveryModel *m_discoveryModel = nullptr; + NymeaHosts *m_nymeaHosts = nullptr; + + AWSClient *m_awsClient = nullptr; UpnpDiscovery *m_upnp = nullptr; ZeroconfDiscovery *m_zeroConf = nullptr; BluetoothServiceDiscovery *m_bluetooth = nullptr; - AWSClient *m_awsClient = nullptr; QTimer m_cloudPollTimer; diff --git a/libnymea-app-core/discovery/upnpdiscovery.cpp b/libnymea-app-core/connection/discovery/upnpdiscovery.cpp similarity index 97% rename from libnymea-app-core/discovery/upnpdiscovery.cpp rename to libnymea-app-core/connection/discovery/upnpdiscovery.cpp index 87a9e861..2853d363 100644 --- a/libnymea-app-core/discovery/upnpdiscovery.cpp +++ b/libnymea-app-core/connection/discovery/upnpdiscovery.cpp @@ -25,9 +25,9 @@ #include #include -UpnpDiscovery::UpnpDiscovery(DiscoveryModel *discoveryModel, QObject *parent) : +UpnpDiscovery::UpnpDiscovery(NymeaHosts *nymeaHosts, QObject *parent) : QObject(parent), - m_discoveryModel(discoveryModel) + m_nymeaHosts(nymeaHosts) { m_networkAccessManager = new QNetworkAccessManager(this); 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; - DiscoveryDevice* device = m_discoveryModel->find(uuid); + NymeaHost* device = m_nymeaHosts->find(uuid); if (!device) { - device = new DiscoveryDevice(m_discoveryModel); + device = new NymeaHost(m_nymeaHosts); device->setUuid(uuid); qDebug() << "UPnP: Adding new host to model"; - m_discoveryModel->addDevice(device); + m_nymeaHosts->addHost(device); } device->setName(name); device->setVersion(version); diff --git a/libnymea-app-core/discovery/upnpdiscovery.h b/libnymea-app-core/connection/discovery/upnpdiscovery.h similarity index 91% rename from libnymea-app-core/discovery/upnpdiscovery.h rename to libnymea-app-core/connection/discovery/upnpdiscovery.h index b5cab329..21e9ebfa 100644 --- a/libnymea-app-core/discovery/upnpdiscovery.h +++ b/libnymea-app-core/connection/discovery/upnpdiscovery.h @@ -27,14 +27,14 @@ #include #include -#include "discoverydevice.h" -#include "discoverymodel.h" +#include "../nymeahost.h" +#include "../nymeahosts.h" class UpnpDiscovery : public QObject { Q_OBJECT public: - explicit UpnpDiscovery(DiscoveryModel *discoveryModel, QObject *parent = 0); + explicit UpnpDiscovery(NymeaHosts *nymeaHosts, QObject *parent = nullptr); bool discovering() const; @@ -49,7 +49,7 @@ private: QTimer m_repeatTimer; - DiscoveryModel *m_discoveryModel; + NymeaHosts *m_nymeaHosts; QHash m_runningReplies; QList m_foundDevices; @@ -57,7 +57,7 @@ private: signals: void discoveringChanged(); void availableChanged(); - void discoveryModelChanged(); + void nymeaHostsChanged(); private slots: void writeDiscoveryPacket(); diff --git a/libnymea-app-core/discovery/zeroconfdiscovery.cpp b/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp similarity index 81% rename from libnymea-app-core/discovery/zeroconfdiscovery.cpp rename to libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp index 2f3053e4..fff26f0c 100644 --- a/libnymea-app-core/discovery/zeroconfdiscovery.cpp +++ b/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp @@ -2,11 +2,11 @@ #include -#include "discoverydevice.h" +#include "../nymeahost.h" -ZeroconfDiscovery::ZeroconfDiscovery(DiscoveryModel *discoveryModel, QObject *parent) : +ZeroconfDiscovery::ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent) : QObject(parent), - m_discoveryModel(discoveryModel) + m_nymeaHosts(nymeaHosts) { #ifdef WITH_ZEROCONF // NOTE: There seem to be too many issues in QtZeroConf and IPv6. @@ -70,6 +70,16 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) 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(); QString uuid; @@ -91,18 +101,18 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) 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); - if (!device) { - device = new DiscoveryDevice(m_discoveryModel); - device->setUuid(uuid); + NymeaHost* host = m_nymeaHosts->find(uuid); + if (!host) { + host = new NymeaHost(m_nymeaHosts); + host->setUuid(uuid); qDebug() << "ZeroConf: Adding new host:" << serverName << uuid; - m_discoveryModel->addDevice(device); + m_nymeaHosts->addHost(host); } - device->setName(serverName); - device->setVersion(version); + host->setName(serverName); + host->setVersion(version); QUrl url; // NOTE: On linux this is "_jsonrpc._tcp" while on apple systems this is "_jsonrpc._tcp." if (entry.type().startsWith("_jsonrpc._tcp")) { @@ -112,12 +122,12 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) } url.setHost(!entry.ip().isNull() ? entry.ip().toString() : entry.ipv6().toString()); url.setPort(entry.port()); - if (!device->connections()->find(url)){ - qDebug() << "Zeroconf: Adding new connection to host:" << device->name() << url.toString(); + if (!host->connections()->find(url)){ + qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString(); QString displayName = QString("%1:%2").arg(url.host()).arg(url.port()); Connection *connection = new Connection(url, Connection::BearerTypeWifi, sslEnabled, displayName); connection->setOnline(true); - device->connections()->addConnection(connection); + host->connections()->addConnection(connection); } } @@ -149,8 +159,8 @@ void ZeroconfDiscovery::serviceEntryRemoved(const QZeroConfService &entry) // qDebug() << "Zeroconf: Service entry removed" << entry.name(); - DiscoveryDevice* device = m_discoveryModel->find(uuid); - if (!device) { + NymeaHost* host = m_nymeaHosts->find(uuid); + if (!host) { // Nothing to do... return; } @@ -163,19 +173,19 @@ void ZeroconfDiscovery::serviceEntryRemoved(const QZeroConfService &entry) } url.setHost(!entry.ip().isNull() ? entry.ip().toString() : entry.ipv6().toString()); url.setPort(entry.port()); - Connection *connection = device->connections()->find(url); + Connection *connection = host->connections()->find(url); if (!connection){ // Connection url not found... return; } // Ok, now we need to remove it - device->connections()->removeConnection(connection); + host->connections()->removeConnection(connection); // 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); + if (host->connections()->rowCount() == 0) { + qDebug() << "Zeroconf: Removing connection from host:" << host->name() << url.toString(); + m_nymeaHosts->removeHost(host); } } #endif diff --git a/libnymea-app-core/discovery/zeroconfdiscovery.h b/libnymea-app-core/connection/discovery/zeroconfdiscovery.h similarity index 78% rename from libnymea-app-core/discovery/zeroconfdiscovery.h rename to libnymea-app-core/connection/discovery/zeroconfdiscovery.h index 39608c33..906a1748 100644 --- a/libnymea-app-core/discovery/zeroconfdiscovery.h +++ b/libnymea-app-core/connection/discovery/zeroconfdiscovery.h @@ -5,7 +5,7 @@ #include "qzeroconf.h" #endif -#include "discoverymodel.h" +#include "../nymeahosts.h" #include @@ -14,14 +14,14 @@ class ZeroconfDiscovery : public QObject Q_OBJECT public: - explicit ZeroconfDiscovery(DiscoveryModel *discoveryModel, QObject *parent = nullptr); + explicit ZeroconfDiscovery(NymeaHosts *nymeaHosts, QObject *parent = nullptr); ~ZeroconfDiscovery(); bool available() const; bool discovering() const; private: - DiscoveryModel *m_discoveryModel; + NymeaHosts *m_nymeaHosts; #ifdef WITH_ZEROCONF QZeroConf *m_zeroconfJsonRPC = nullptr; diff --git a/libnymea-app-core/connection/nymeaconnection.cpp b/libnymea-app-core/connection/nymeaconnection.cpp index 36f57efc..9be30e90 100644 --- a/libnymea-app-core/connection/nymeaconnection.cpp +++ b/libnymea-app-core/connection/nymeaconnection.cpp @@ -1,4 +1,5 @@ #include "nymeaconnection.h" +#include "nymeahost.h" #include #include @@ -14,51 +15,18 @@ NymeaConnection::NymeaConnection(QObject *parent) : QObject(parent) { -} + m_networkConfigManager = new QNetworkConfigurationManager(this); -bool NymeaConnection::connect(const QString &url) -{ - if (connected()) { - qWarning() << "Already connected. Cannot connect multiple times"; - return false; - } + QObject::connect(m_networkConfigManager, &QNetworkConfigurationManager::configurationAdded, this, [this](const QNetworkConfiguration &config){ +// qDebug() << "Network configuration added:" << config.name() << config.bearerTypeName() << config.purpose(); + updateActiveBearers(); + }); + 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); - 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 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(); + updateActiveBearers(); } void NymeaConnection::acceptCertificate(const QString &url, const QByteArray &pem) @@ -84,30 +52,56 @@ bool NymeaConnection::isTrusted(const QString &url) return false; } +Connection::BearerTypes NymeaConnection::availableBearerTypes() const +{ + return m_availableBearerTypes; +} + 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 +NymeaHost *NymeaConnection::currentHost() const { - return m_currentUrl.toString(); + return m_currentHost; } -QString NymeaConnection::hostAddress() const +void NymeaConnection::setCurrentHost(NymeaHost *host) { - return m_currentUrl.host(); + 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(); + + if (m_currentHost) { + connectInternal(m_currentHost); + } } -int NymeaConnection::port() const +Connection *NymeaConnection::currentConnection() const { - return m_currentUrl.port(); -} - -QString NymeaConnection::bluetoothAddress() const -{ - QUrlQuery query(m_currentUrl); - return query.queryItemValue("mac"); + if (!m_currentHost || !m_currentTransport) { + return nullptr; + } + return m_transportCandidates.value(m_currentTransport); } void NymeaConnection::sendData(const QByteArray &data) @@ -122,15 +116,16 @@ void NymeaConnection::sendData(const QByteArray &data) void NymeaConnection::onSslErrors(const QList &errors) { - qDebug() << "Connection: SSL errors:" << errors; + NymeaTransportInterface *transport = qobject_cast(sender()); + + qDebug() << "SSL errors for url:" << transport->url(); QList ignoredErrors; foreach (const QSslError &error, errors) { + qDebug() << error.errorString(); if (error.error() == QSslError::HostNameMismatch) { qDebug() << "Ignoring host mismatch on certificate."; ignoredErrors.append(error); } else if (error.error() == QSslError::SelfSignedCertificate || error.error() == QSslError::CertificateUntrusted) { - qDebug() << "have a self signed certificate." << error.certificate(); - // Check our cert DB QByteArray pem; @@ -140,7 +135,7 @@ void NymeaConnection::onSslErrors(const QList &errors) // However, we want to emit verifyConnectionCertificate in any case here. QSettings settings; settings.beginGroup("acceptedCertificates"); - QByteArray storedFingerPrint = settings.value(m_currentUrl.host()).toByteArray(); + QByteArray storedFingerPrint = settings.value(transport->url().host()).toByteArray(); settings.endGroup(); QByteArray certificateFingerprint; @@ -158,15 +153,18 @@ void NymeaConnection::onSslErrors(const QList &errors) ignoredErrors.append(error); // 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 - } 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."; ignoredErrors.append(error); // Ok... nothing found... Pop up the message } else { + qDebug() << "Host presents an unknown self signed certificate:" << error.certificate(); + qDebug() << "Asking user for confirmation."; + QStringList info; info << tr("Common Name:") << error.certificate().issuerInfo(QSslCertificate::CommonName); info << tr("Oragnisation:") < &errors) // info << tr("Name Qualifier:")<< error.certificate().issuerInfo(QSslCertificate::DistinguishedNameQualifier); // info << tr("Email:")<< error.certificate().issuerInfo(QSslCertificate::EmailAddress); - emit verifyConnectionCertificate(m_currentUrl.toString(), info, certificateFingerprint, error.certificate().toPem()); + emit verifyConnectionCertificate(transport->url().toString(), info, certificateFingerprint, error.certificate().toPem()); } } else { // Reject the connection on all other errors... @@ -187,38 +185,141 @@ void NymeaConnection::onSslErrors(const QList &errors) if (ignoredErrors == errors) { // 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... - m_currentTransport->ignoreSslErrors(ignoredErrors); + transport->ignoreSslErrors(ignoredErrors); } } void NymeaConnection::onError(QAbstractSocket::SocketError error) { QMetaEnum errorEnum = QMetaEnum::fromType(); - emit connectionError(errorEnum.valueToKey(error)); + QString errorString = errorEnum.valueToKey(error); + + NymeaTransportInterface* transport = qobject_cast(sender()); + + if (transport == m_currentTransport) { + qDebug() << "Current transport failed:" << error; + // The current transport failed, forward the error + emit connectionError(errorString); + return; + } + + if (!m_currentTransport) { + // We're trying to connect and one of the transports failed... + qDebug() << "A transport error happened for" << transport->url() << error; + if (m_transportCandidates.contains(transport)) { + m_transportCandidates.remove(transport); + transport->deleteLater(); + } + if (m_transportCandidates.isEmpty()) { + emit connectionError(errorString); + } + } } void NymeaConnection::onConnected() { - if (m_currentTransport != sender()) { - qWarning() << "NymeaConnection: An inactive transport is emitting signals... ignoring."; + NymeaTransportInterface* newTransport = qobject_cast(sender()); + 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(); + 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; } - qDebug() << "NymeaConnection: connected."; - emit connectedChanged(true); } void NymeaConnection::onDisconnected() { - if (m_currentTransport != sender()) { - qWarning() << "NymeaConnection: An inactive transport is emitting signals... ignoring."; + NymeaTransportInterface* t = qobject_cast(sender()); + 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(); return; } + m_transportCandidates.remove(m_currentTransport); m_currentTransport->deleteLater(); m_currentTransport = nullptr; + emit currentConnectionChanged(); + qDebug() << "NymeaConnection: disconnected."; emit connectedChanged(false); + + connectInternal(m_currentHost); } +void NymeaConnection::updateActiveBearers() +{ + Connection::BearerTypes availableBearerTypes; + QList 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(); + 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... + return; + } + if (!m_currentTransport) { + // There's a host but no connection. Try connecting 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; + default: + qWarning() << "Unhandled Bearer Type Family:" << type; + } + return Connection::BearerTypeNone; +} + + bool NymeaConnection::storePem(const QUrl &host, const QByteArray &pem) { QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/sslcerts/"); @@ -249,6 +350,70 @@ bool NymeaConnection::loadPem(const QUrl &host, QByteArray &pem) void NymeaConnection::registerTransport(NymeaTransportInterfaceFactory *transportFactory) { foreach (const QString &scheme, transportFactory->supportedSchemes()) { - m_transports[scheme] = transportFactory; + m_transportFactories[scheme] = transportFactory; } } + +void NymeaConnection::connect(NymeaHost *nymeaHost) +{ + setCurrentHost(nymeaHost); +} + +void NymeaConnection::connectInternal(NymeaHost *host) +{ + 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); + } + } + + if (m_availableBearerTypes.testFlag(Connection::BearerTypeCloud)) { + Connection* wanConnection = host->connections()->bestMatch(Connection::BearerTypeCloud); + if (wanConnection) { + qDebug() << "Best candidate WAN connection:" << wanConnection->url(); + connectInternal(wanConnection); + } + } +} + +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 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); +} diff --git a/libnymea-app-core/connection/nymeaconnection.h b/libnymea-app-core/connection/nymeaconnection.h index 6bb0cda3..91771d68 100644 --- a/libnymea-app-core/connection/nymeaconnection.h +++ b/libnymea-app-core/connection/nymeaconnection.h @@ -6,6 +6,10 @@ #include #include #include +#include + + +#include "nymeahost.h" class NymeaTransportInterface; class NymeaTransportInterfaceFactory; @@ -14,34 +18,37 @@ class NymeaConnection : public QObject { Q_OBJECT Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged) - Q_PROPERTY(QString url READ url NOTIFY currentUrlChanged) - Q_PROPERTY(QString hostAddress READ hostAddress NOTIFY currentUrlChanged) - Q_PROPERTY(int port READ port NOTIFY currentUrlChanged) - Q_PROPERTY(QString bluetoothAddress READ bluetoothAddress NOTIFY currentUrlChanged) + Q_PROPERTY(NymeaHost* currentHost READ currentHost WRITE setCurrentHost NOTIFY currentHostChanged) + Q_PROPERTY(Connection* currentConnection READ currentConnection NOTIFY currentConnectionChanged) + Q_PROPERTY(Connection::BearerTypes availableBearerTypes READ availableBearerTypes NOTIFY availableBearerTypesChanged) public: explicit NymeaConnection(QObject *parent = nullptr); void registerTransport(NymeaTransportInterfaceFactory *transportFactory); - Q_INVOKABLE bool connect(const QString &url); + Q_INVOKABLE void connect(NymeaHost* nymeaHost); Q_INVOKABLE void disconnect(); Q_INVOKABLE void acceptCertificate(const QString &url, const QByteArray &pem); Q_INVOKABLE bool isTrusted(const QString &url); + Connection::BearerTypes availableBearerTypes() const; + bool connected(); - QString url() const; - QString hostAddress() const; - int port() const; - QString bluetoothAddress() const; + NymeaHost* currentHost() const; + void setCurrentHost(NymeaHost *host); + + Connection* currentConnection() const; void sendData(const QByteArray &data); signals: - void currentUrlChanged(); + void availableBearerTypesChanged(); void verifyConnectionCertificate(const QString &url, const QStringList &issuerInfo, const QByteArray &fingerprint, const QByteArray &pem); + void currentHostChanged(); void connectedChanged(bool connected); + void currentConnectionChanged(); void connectionError(const QString &error); void dataAvailable(const QByteArray &data); @@ -51,14 +58,24 @@ private slots: void onConnected(); void onDisconnected(); + void updateActiveBearers(); private: bool storePem(const QUrl &host, const 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: - QHash m_transports; + QNetworkConfigurationManager *m_networkConfigManager = nullptr; + Connection::BearerTypes m_availableBearerTypes = Connection::BearerTypeNone; + + QHash m_transportFactories; + QHash m_transportCandidates; NymeaTransportInterface *m_currentTransport = nullptr; - QUrl m_currentUrl; + NymeaHost *m_currentHost = nullptr; }; #endif // NYMEACONNECTION_H diff --git a/libnymea-app-core/discovery/discoverydevice.cpp b/libnymea-app-core/connection/nymeahost.cpp similarity index 77% rename from libnymea-app-core/discovery/discoverydevice.cpp rename to libnymea-app-core/connection/nymeahost.cpp index d04a265d..1b920e6a 100644 --- a/libnymea-app-core/discovery/discoverydevice.cpp +++ b/libnymea-app-core/connection/nymeahost.cpp @@ -18,32 +18,41 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#include "discoverydevice.h" +#include "nymeahost.h" #include -DiscoveryDevice::DiscoveryDevice(QObject *parent): +NymeaHost::NymeaHost(QObject *parent): QObject(parent), m_connections(new Connections(this)) { + connect(m_connections, &Connections::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, const QVector){ + 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; } -void DiscoveryDevice::setUuid(const QUuid &uuid) +void NymeaHost::setUuid(const QUuid &uuid) { m_uuid = uuid; } -QString DiscoveryDevice::name() const +QString NymeaHost::name() const { return m_name; } -void DiscoveryDevice::setName(const QString &name) +void NymeaHost::setName(const QString &name) { if (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; } -void DiscoveryDevice::setVersion(const QString &version) +void NymeaHost::setVersion(const QString &version) { if (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; } @@ -159,40 +168,21 @@ Connection* Connections::get(int index) const return nullptr; } -Connection* Connections::bestMatch() const +Connection *Connections::bestMatch(Connection::BearerTypes bearerTypes) const { - QList bearerPreference = {Connection::BearerTypeEthernet, Connection::BearerTypeWifi, Connection::BearerTypeCloud, Connection::BearerTypeBluetooth, Connection::BearerTypeUnknown}; + QList bearerPreference = {Connection::BearerTypeEthernet, Connection::BearerTypeWifi, Connection::BearerTypeCloud, Connection::BearerTypeBluetooth, Connection::BearerTypeNone}; Connection *best = nullptr; +// qDebug() << "Bestmatch" << m_connections.count(); foreach (Connection *c, m_connections) { +// qDebug() << "have connection:" << bearerTypes << c->url() << bearerTypes.testFlag(c->bearerType()); + if (!bearerTypes.testFlag(c->bearerType())) { + continue; + } if (!best) { best = c; continue; } - uint oldBearerPriority = static_cast(bearerPreference.indexOf(best->bearerType())); - uint newBearerPriority = static_cast(bearerPreference.indexOf(c->bearerType())); - if (newBearerPriority < oldBearerPriority) { - // New one has better bearer, switch - best = c; - continue; - } - if (oldBearerPriority < newBearerPriority) { - // Discard new one as the existing is on a better bearer - continue; - } - - // Same bearer, prefer secure over insecure - if (!best->secure() && c->secure()) { - // New one is secure, old one not. switch - best = c; - continue; - } - if (best->secure() && !c->secure()) { - // Old one is secure, new one isn't, skip new one - continue; - } - - // both options are now on the same bearer and either secure or insecure, prefer nymearpc over websocket for less overhead - if (best->url().scheme().startsWith("ws") && c->url().scheme().startsWith("nymea")) { + if (c->priority() > best->priority()) { best = c; } } @@ -252,3 +242,31 @@ void Connection::setOnline(bool online) emit onlineChanged(); } } + +int Connection::priority() const +{ + int prio = 0; + 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 += 5; +// } + return prio; +} diff --git a/libnymea-app-core/discovery/discoverydevice.h b/libnymea-app-core/connection/nymeahost.h similarity index 84% rename from libnymea-app-core/discovery/discoverydevice.h rename to libnymea-app-core/connection/nymeahost.h index 8394a48a..52659e53 100644 --- a/libnymea-app-core/discovery/discoverydevice.h +++ b/libnymea-app-core/connection/nymeahost.h @@ -18,8 +18,8 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#ifndef DISCOVERYDEVICE_H -#define DISCOVERYDEVICE_H +#ifndef NYMEAHOST_H +#define NYMEAHOST_H #include #include @@ -36,15 +36,19 @@ class Connection: public QObject { Q_PROPERTY(bool secure READ secure CONSTANT) Q_PROPERTY(QString displayName READ displayName CONSTANT) Q_PROPERTY(bool online READ online NOTIFY onlineChanged) + Q_PROPERTY(int priority READ priority NOTIFY priorityChanged) + public: enum BearerType { - BearerTypeUnknown, - BearerTypeWifi, - BearerTypeEthernet, - BearerTypeBluetooth, - BearerTypeCloud + BearerTypeNone = 0x00, + BearerTypeWifi = 0x01, + BearerTypeEthernet = 0x02, + BearerTypeBluetooth = 0x04, + BearerTypeCloud = 0x08, + BearerTypeAll = 0xFF }; Q_ENUM(BearerType) + Q_DECLARE_FLAGS(BearerTypes, BearerType) Connection(const QUrl &url, BearerType bearerType, bool secure, const QString &displayName, QObject *parent = nullptr); @@ -54,13 +58,15 @@ public: QString displayName() const; bool online() const; void setOnline(bool online); + int priority() const; signals: void onlineChanged(); + void priorityChanged(); private: QUrl m_url; - BearerType m_bearerType = BearerTypeUnknown; + BearerType m_bearerType = BearerTypeNone; bool m_secure = false; QString m_displayName; bool m_online = false; @@ -89,23 +95,22 @@ public: Q_INVOKABLE Connection* find(const QUrl &url) const; Q_INVOKABLE Connection* get(int index) const; - - Connection *bestMatch() const; + Q_INVOKABLE Connection* bestMatch(Connection::BearerTypes bearerTypes = Connection::BearerTypeAll) const; signals: + void countChanged(); void connectionAdded(Connection *connection); void connectionRemoved(Connection *connection); - void countChanged(); protected: QHash roleNames() const override; private: QList m_connections; - }; +Q_DECLARE_OPERATORS_FOR_FLAGS(Connection::BearerTypes) -class DiscoveryDevice: public QObject +class NymeaHost: public QObject { Q_OBJECT Q_PROPERTY(QUuid uuid READ uuid CONSTANT) @@ -114,7 +119,7 @@ class DiscoveryDevice: public QObject Q_PROPERTY(Connections* connections READ connections CONSTANT) public: - explicit DiscoveryDevice(QObject *parent = nullptr); + explicit NymeaHost(QObject *parent = nullptr); QUuid uuid() const; void setUuid(const QUuid &uuid); @@ -130,6 +135,7 @@ public: signals: void nameChanged(); void versionChanged(); + void connectionChanged(); private: QUuid m_uuid; @@ -138,4 +144,4 @@ private: Connections *m_connections = nullptr; }; -#endif // DISCOVERYDEVICE_H +#endif // NYMEAHOST_H diff --git a/libnymea-app-core/connection/nymeahosts.cpp b/libnymea-app-core/connection/nymeahosts.cpp new file mode 100644 index 00000000..2fe4d4d0 --- /dev/null +++ b/libnymea-app-core/connection/nymeahosts.cpp @@ -0,0 +1,204 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2015 Simon Stuerz * + * * + * 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 . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "nymeahosts.h" +#include "connection/discovery/nymeadiscovery.h" +#include "nymeahost.h" +#include "connection/nymeaconnection.h" + +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::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 NymeaHosts::roleNames() const +{ + QHash 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(); + } +} + +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(); + if (m_nymeaConnection->availableBearerTypes().testFlag(host->connections()->get(i)->bearerType())) { + hasReachableConnection = true; + break; + } + } + if (!hasReachableConnection) { + return false; + } + } + return true; +} diff --git a/libnymea-app-core/discovery/discoverymodel.h b/libnymea-app-core/connection/nymeahosts.h similarity index 51% rename from libnymea-app-core/discovery/discoverymodel.h rename to libnymea-app-core/connection/nymeahosts.h index bb677bd1..664f64d9 100644 --- a/libnymea-app-core/discovery/discoverymodel.h +++ b/libnymea-app-core/connection/nymeahosts.h @@ -18,51 +18,91 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -#ifndef DISCOVERYMODEL_H -#define DISCOVERYMODEL_H +#ifndef NYMEAHOSTS_H +#define NYMEAHOSTS_H #include #include #include +#include -class DiscoveryDevice; +class NymeaHost; +class NymeaDiscovery; +class NymeaConnection; -class DiscoveryModel : public QAbstractListModel +class NymeaHosts : public QAbstractListModel { Q_OBJECT Q_PROPERTY(int count READ rowCount NOTIFY countChanged) public: - enum DeviceRole { - DeviceTypeRole, + enum HostRole { UuidRole, NameRole, VersionRole }; - Q_ENUM(DeviceRole) + Q_ENUM(HostRole) - explicit DiscoveryModel(QObject *parent = nullptr); + explicit NymeaHosts(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); + void addHost(NymeaHost *host); + void removeHost(NymeaHost *host); - Q_INVOKABLE DiscoveryDevice *get(int index) const; - Q_INVOKABLE DiscoveryDevice *find(const QUuid &uuid); + Q_INVOKABLE NymeaHost *get(int index) const; + Q_INVOKABLE NymeaHost *find(const QUuid &uuid); void clearModel(); signals: - void deviceAdded(DiscoveryDevice* device); - void deviceRemoved(DiscoveryDevice* device); + void hostAdded(NymeaHost* host); + void hostRemoved(NymeaHost* host); void countChanged(); + void hostChanged(); protected: QHash roleNames() const; private: - QList m_devices; + QList m_hosts; }; -#endif // DISCOVERYMODEL_H +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); + +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 diff --git a/libnymea-app-core/connection/nymeatransportinterface.h b/libnymea-app-core/connection/nymeatransportinterface.h index 5ccb6aec..ca9b9e77 100644 --- a/libnymea-app-core/connection/nymeatransportinterface.h +++ b/libnymea-app-core/connection/nymeatransportinterface.h @@ -53,6 +53,7 @@ public: virtual ~NymeaTransportInterface() = default; virtual bool connect(const QUrl &url) = 0; + virtual QUrl url() const = 0; virtual void disconnect() = 0; virtual ConnectionState connectionState() const = 0; virtual void sendData(const QByteArray &data) = 0; diff --git a/libnymea-app-core/connection/tcpsockettransport.cpp b/libnymea-app-core/connection/tcpsockettransport.cpp index 87d94151..3f2c17ed 100644 --- a/libnymea-app-core/connection/tcpsockettransport.cpp +++ b/libnymea-app-core/connection/tcpsockettransport.cpp @@ -5,7 +5,6 @@ TcpSocketTransport::TcpSocketTransport(QObject *parent) : NymeaTransportInterface(parent) { 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); typedef void (QSslSocket:: *sslErrorsSignal)(const QList &); QObject::connect(&m_socket, static_cast(&QSslSocket::sslErrors), this, &TcpSocketTransport::sslErrors); @@ -58,6 +57,11 @@ bool TcpSocketTransport::connect(const QUrl &url) return false; } +QUrl TcpSocketTransport::url() const +{ + return m_url; +} + NymeaTransportInterface::ConnectionState TcpSocketTransport::connectionState() const { switch (m_socket.state()) { @@ -91,6 +95,9 @@ void TcpSocketTransport::socketReadyRead() void TcpSocketTransport::onSocketStateChanged(const QAbstractSocket::SocketState &state) { qDebug() << "Socket state changed -->" << state; + if (state == QAbstractSocket::UnconnectedState) { + emit disconnected(); + } } NymeaTransportInterface *TcpSocketTransportFactory::createTransport(QObject *parent) const diff --git a/libnymea-app-core/connection/tcpsockettransport.h b/libnymea-app-core/connection/tcpsockettransport.h index 2d2cb822..185071c6 100644 --- a/libnymea-app-core/connection/tcpsockettransport.h +++ b/libnymea-app-core/connection/tcpsockettransport.h @@ -21,6 +21,7 @@ public: explicit TcpSocketTransport(QObject *parent = nullptr); bool connect(const QUrl &url) override; + QUrl url() const override; ConnectionState connectionState() const override; void disconnect() override; void sendData(const QByteArray &data) override; diff --git a/libnymea-app-core/connection/websockettransport.cpp b/libnymea-app-core/connection/websockettransport.cpp index ff2c56f7..34d6f215 100644 --- a/libnymea-app-core/connection/websockettransport.cpp +++ b/libnymea-app-core/connection/websockettransport.cpp @@ -42,10 +42,16 @@ WebsocketTransport::WebsocketTransport(QObject *parent) : bool WebsocketTransport::connect(const QUrl &url) { + m_url = url; m_socket->open(QUrl(url)); return true; } +QUrl WebsocketTransport::url() const +{ + return m_url; +} + NymeaTransportInterface::ConnectionState WebsocketTransport::connectionState() const { switch (m_socket->state()) { diff --git a/libnymea-app-core/connection/websockettransport.h b/libnymea-app-core/connection/websockettransport.h index be2d4223..7256a002 100644 --- a/libnymea-app-core/connection/websockettransport.h +++ b/libnymea-app-core/connection/websockettransport.h @@ -40,12 +40,14 @@ public: explicit WebsocketTransport(QObject *parent = nullptr); bool connect(const QUrl &url) override; + QUrl url() const override; ConnectionState connectionState() const override; void disconnect() override; void sendData(const QByteArray &data) override; void ignoreSslErrors(const QList &errors) override; private: + QUrl m_url; QWebSocket *m_socket; private slots: diff --git a/libnymea-app-core/discovery/discoverymodel.cpp b/libnymea-app-core/discovery/discoverymodel.cpp deleted file mode 100644 index 25c7825d..00000000 --- a/libnymea-app-core/discovery/discoverymodel.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Copyright (C) 2015 Simon Stuerz * - * * - * 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 . * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#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 deviceAdded(device); - 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 deviceRemoved(device); - 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 DiscoveryModel::roleNames() const -{ - QHash roles; - roles[UuidRole] = "uuid"; - roles[NameRole] = "name"; - roles[VersionRole] = "version"; - return roles; -} diff --git a/libnymea-app-core/discovery/nymeadiscovery.cpp b/libnymea-app-core/discovery/nymeadiscovery.cpp deleted file mode 100644 index c395cc22..00000000 --- a/libnymea-app-core/discovery/nymeadiscovery.cpp +++ /dev/null @@ -1,204 +0,0 @@ -#include "nymeadiscovery.h" -#include "upnpdiscovery.h" -#include "zeroconfdiscovery.h" -#include "bluetoothservicediscovery.h" -#include "connection/awsclient.h" - -#include -#include -#include -#include -#include - -NymeaDiscovery::NymeaDiscovery(QObject *parent) : QObject(parent) -{ - m_discoveryModel = new DiscoveryModel(this); - connect(m_discoveryModel, &DiscoveryModel::deviceAdded, this, [this](DiscoveryDevice *device) { - if (!m_pendingHostResolutions.contains(device->uuid())) { - return; - } - Connection *c = device->connections()->bestMatch(); - if (!c) { - qDebug() << "Host found but there isn't a valid candidate yet?"; - connect(device->connections(), &Connections::connectionAdded, this, [this, device](Connection *connection) { - if (m_pendingHostResolutions.contains(device->uuid())) { - qDebug() << "Host" << device->uuid() << "resolved to" << connection->url().toString(); - m_pendingHostResolutions.removeAll(device->uuid()); - emit serverUuidResolved(device->uuid(), connection->url().toString()); - } - }); - return; - } - qDebug() << "Host" << device->uuid() << "appeared! Best match is" << c->url(); - m_pendingHostResolutions.removeAll(device->uuid()); - emit serverUuidResolved(device->uuid(), c->url().toString()); - }); - - 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(); - } - }); - - - QNetworkConfigurationManager manager; - QList configs = manager.allConfigurations(QNetworkConfiguration::Active); - - foreach (const QNetworkConfiguration &config, configs) { - if (config.purpose() != QNetworkConfiguration::PublicPurpose) { - continue; - } - if (config.bearerType() != QNetworkConfiguration::BearerWLAN && config.bearerType() != QNetworkConfiguration::BearerEthernet) { - continue; - } - qDebug() << "Have Network configuration:" << config.name() << config.bearerTypeName() << config.purpose() << config.type(); - } -} - -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) { - m_awsClient->fetchDevices(); - connect(m_awsClient, &AWSClient::devicesFetched, this, &NymeaDiscovery::syncCloudDevices); - syncCloudDevices(); - } -} - -void NymeaDiscovery::resolveServerUuid(const QUuid &uuid) -{ - // Do we already know this host? - DiscoveryDevice *dev = m_discoveryModel->find(uuid); - if (!dev) { - qDebug() << "Host" << uuid << "not known yet..."; - m_pendingHostResolutions.append(uuid); - return; - } - Connection *c = dev->connections()->bestMatch(); - if (!c) { - qDebug() << "Host" << uuid << "is known but doesn't have a usable connection option yet."; - m_pendingHostResolutions.append(uuid); - return; - } - qDebug() << "Host" << uuid << "is known. Best match is" << c->url(); - emit serverUuidResolved(uuid, c->url().toString()); -} - -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 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()); - } -} - diff --git a/libnymea-app-core/discovery/nymeahost.cpp b/libnymea-app-core/discovery/nymeahost.cpp deleted file mode 100644 index f07614f0..00000000 --- a/libnymea-app-core/discovery/nymeahost.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Copyright (C) 2015 Simon Stuerz * - * * - * 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 . * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#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; -} - - diff --git a/libnymea-app-core/discovery/nymeahost.h b/libnymea-app-core/discovery/nymeahost.h deleted file mode 100644 index 8c6efef9..00000000 --- a/libnymea-app-core/discovery/nymeahost.h +++ /dev/null @@ -1,54 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Copyright (C) 2015 Simon Stuerz * - * * - * 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 . * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#ifndef NYMEAHOST_H -#define NYMEAHOST_H - -#include -#include -#include - -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 diff --git a/libnymea-app-core/discovery/nymeahosts.cpp b/libnymea-app-core/discovery/nymeahosts.cpp deleted file mode 100644 index 13dccf20..00000000 --- a/libnymea-app-core/discovery/nymeahosts.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Copyright (C) 2015 Simon Stuerz * - * * - * 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 . * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#include "nymeahosts.h" -#include "nymeahost.h" - -#include -#include -#include - -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 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 NymeaHosts::roleNames() const -{ - QHash roles; - roles[NameRole] = "name"; - roles[HostAddressRole] = "hostAddress"; - roles[WebSocketUrlRole] = "webSocketUrl"; - return roles; -} diff --git a/libnymea-app-core/discovery/nymeahosts.h b/libnymea-app-core/discovery/nymeahosts.h deleted file mode 100644 index d705b417..00000000 --- a/libnymea-app-core/discovery/nymeahosts.h +++ /dev/null @@ -1,59 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * Copyright (C) 2015 Simon Stuerz * - * * - * 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 . * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -#ifndef NYMEAHOSTS_H -#define NYMEAHOSTS_H - -#include - -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 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 roleNames() const; - -private: - QList m_hosts; - -}; - -#endif // NYMEAHOSTS_H diff --git a/libnymea-app-core/libnymea-app-core.h b/libnymea-app-core/libnymea-app-core.h index d4402af8..b21ffe36 100644 --- a/libnymea-app-core/libnymea-app-core.h +++ b/libnymea-app-core/libnymea-app-core.h @@ -2,14 +2,14 @@ #define LIBNYMEAAPPCORE_H #include "engine.h" +#include "connection/nymeahosts.h" +#include "connection/nymeahost.h" +#include "connection/discovery/nymeadiscovery.h" #include "vendorsproxy.h" #include "deviceclassesproxy.h" #include "devicesproxy.h" #include "pluginsproxy.h" #include "devicediscovery.h" -#include "discovery/nymeadiscovery.h" -#include "discovery/discoverymodel.h" -#include "discovery/discoverydevice.h" #include "interfacesmodel.h" #include "rulemanager.h" #include "models/rulesfiltermodel.h" @@ -159,9 +159,10 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "MqttPolicies", "Get it from NymeaConfiguration"); qmlRegisterType(uri, 1, 0, "NymeaDiscovery"); - qmlRegisterUncreatableType(uri, 1, 0, "DiscoveryModel", "Get it from NymeaDiscovery"); - qmlRegisterUncreatableType(uri, 1, 0, "DiscoveryDevice", "Get it from DiscoveryModel"); - qmlRegisterUncreatableType(uri, 1, 0, "Connection", "Get it from DiscoveryDevice"); + qmlRegisterUncreatableType(uri, 1, 0, "NymeaHosts", "Get it from NymeaDiscovery"); + qmlRegisterType(uri, 1, 0, "NymeaHostsFilterModel"); + qmlRegisterUncreatableType(uri, 1, 0, "NymeaHost", "Get it from NymeaHosts"); + qmlRegisterUncreatableType(uri, 1, 0, "Connection", "Get it from NymeaHost"); qmlRegisterType(uri, 1, 0, "LogsModel"); qmlRegisterType(uri, 1, 0, "LogsModelNg"); diff --git a/libnymea-app-core/libnymea-app-core.pro b/libnymea-app-core/libnymea-app-core.pro index 271b9531..70858764 100644 --- a/libnymea-app-core/libnymea-app-core.pro +++ b/libnymea-app-core/libnymea-app-core.pro @@ -25,19 +25,22 @@ INCLUDEPATH += $$top_srcdir/libnymea-common \ SOURCES += \ engine.cpp \ + connection/nymeahost.cpp \ + connection/nymeahosts.cpp \ connection/nymeaconnection.cpp \ connection/nymeatransportinterface.cpp \ connection/websockettransport.cpp \ connection/tcpsockettransport.cpp \ connection/bluetoothtransport.cpp \ connection/awsclient.cpp \ + connection/discovery/nymeadiscovery.cpp \ + connection/discovery/upnpdiscovery.cpp \ + connection/discovery/zeroconfdiscovery.cpp \ + connection/discovery/bluetoothservicediscovery.cpp \ devicemanager.cpp \ jsonrpc/jsontypes.cpp \ jsonrpc/jsonrpcclient.cpp \ jsonrpc/jsonhandler.cpp \ - discovery/nymeahost.cpp \ - discovery/nymeahosts.cpp \ - discovery/upnpdiscovery.cpp \ devices.cpp \ devicesproxy.cpp \ deviceclasses.cpp \ @@ -46,14 +49,10 @@ SOURCES += \ vendorsproxy.cpp \ pluginsproxy.cpp \ interfacesmodel.cpp \ - discovery/zeroconfdiscovery.cpp \ - discovery/discoverydevice.cpp \ - discovery/discoverymodel.cpp \ rulemanager.cpp \ models/rulesfiltermodel.cpp \ models/logsmodel.cpp \ models/valuelogsproxymodel.cpp \ - discovery/nymeadiscovery.cpp \ logmanager.cpp \ wifisetup/bluetoothdevice.cpp \ wifisetup/bluetoothdeviceinfo.cpp \ @@ -74,7 +73,6 @@ SOURCES += \ ruletemplates/ruleactiontemplate.cpp \ ruletemplates/stateevaluatortemplate.cpp \ ruletemplates/statedescriptortemplate.cpp \ - discovery/bluetoothservicediscovery.cpp \ connection/cloudtransport.cpp \ connection/sigv4utils.cpp \ ruletemplates/ruleactionparamtemplate.cpp \ @@ -88,6 +86,8 @@ SOURCES += \ HEADERS += \ engine.h \ + connection/nymeahost.h \ + connection/nymeahosts.h \ connection/nymeaconnection.h \ connection/nymeatransportinterface.h \ connection/websockettransport.h \ @@ -95,13 +95,14 @@ HEADERS += \ connection/bluetoothtransport.h \ connection/awsclient.h \ connection/sigv4utils.h \ + connection/discovery/nymeadiscovery.h \ + connection/discovery/upnpdiscovery.h \ + connection/discovery/zeroconfdiscovery.h \ + connection/discovery/bluetoothservicediscovery.h \ devicemanager.h \ jsonrpc/jsontypes.h \ jsonrpc/jsonrpcclient.h \ jsonrpc/jsonhandler.h \ - discovery/nymeahost.h \ - discovery/nymeahosts.h \ - discovery/upnpdiscovery.h \ devices.h \ devicesproxy.h \ deviceclasses.h \ @@ -110,14 +111,10 @@ HEADERS += \ vendorsproxy.h \ pluginsproxy.h \ interfacesmodel.h \ - discovery/zeroconfdiscovery.h \ - discovery/discoverydevice.h \ - discovery/discoverymodel.h \ rulemanager.h \ models/rulesfiltermodel.h \ models/logsmodel.h \ models/valuelogsproxymodel.h \ - discovery/nymeadiscovery.h \ logmanager.h \ wifisetup/bluetoothdevice.h \ wifisetup/bluetoothdeviceinfo.h \ @@ -139,7 +136,6 @@ HEADERS += \ ruletemplates/ruleactiontemplate.h \ ruletemplates/stateevaluatortemplate.h \ ruletemplates/statedescriptortemplate.h \ - discovery/bluetoothservicediscovery.h \ connection/cloudtransport.h \ ruletemplates/ruleactionparamtemplate.h \ configuration/serverconfiguration.h \ diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index 39ba3c66..a3814403 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -57,6 +57,12 @@ QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine) 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); QApplication application(argc, argv); application.setApplicationName("nymea-app"); diff --git a/nymea-app/platformintegration/android/platformhelperandroid.cpp b/nymea-app/platformintegration/android/platformhelperandroid.cpp index 0c7ee1e4..1e482976 100644 --- a/nymea-app/platformintegration/android/platformhelperandroid.cpp +++ b/nymea-app/platformintegration/android/platformhelperandroid.cpp @@ -59,10 +59,10 @@ void PlatformHelperAndroid::vibrate(PlatformHelper::HapticsFeedback feedbackType int duration; switch (feedbackType) { case HapticsFeedbackSelection: - duration = 15; + duration = 20; break; case HapticsFeedbackImpact: - duration = 25; + duration = 30; break; case HapticsFeedbackNotification: duration = 500; diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 747ece62..c40b307d 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -57,6 +57,7 @@ ApplicationWindow { awsClient: AWSClient // discovering: pageStack.currentItem.objectName === "discoveryPage" } + property alias _discovery: discovery onClosing: { rootItem.handleCloseEvent(close) diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index 59a60795..93979105 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 import Nymea 1.0 import "components" +import "connection" Item { id: root @@ -74,7 +75,7 @@ Item { height: swipeView.height width: swipeView.width objectName: "pageStack" - initialItem: Page {} + initialItem: ConnectPage {} property var tabSettings: Settings { category: "tabSettings" + index @@ -88,7 +89,7 @@ Item { readonly property Engine engine: engineObject readonly property Engine _engine: engineObject // In case a child cannot use "engine" property int connectionTabIndex: index - onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.connection.url +// onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.connection.url Binding { target: AWSClient @@ -97,20 +98,26 @@ Item { } Component.onCompleted: { - pageStack.push(Qt.resolvedUrl("connection/ConnectPage.qml"), StackView.Immediate) - setupPushNotifications(); +// pageStack.push(Qt.resolvedUrl("connection/ConnectPage.qml"), StackView.Immediate) +// setupPushNotifications(); } 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() - if (!engine.connection.connected) { + if (!engine.connection.currentHost) { pageStack.push(Qt.resolvedUrl("connection/ConnectPage.qml")) PlatformHelper.hideSplashScreen(); return; } + if (engine.jsonRpcClient.connected) { + pageStack.push(Qt.resolvedUrl("MainPage.qml")) + PlatformHelper.hideSplashScreen(); + return; + } if (engine.jsonRpcClient.authenticationRequired || engine.jsonRpcClient.initialSetupRequired) { + PlatformHelper.hideSplashScreen(); if (engine.jsonRpcClient.pushButtonAuthAvailable) { print("opening push button auth") var page = pageStack.push(Qt.resolvedUrl("PushButtonAuthPage.qml")) @@ -127,10 +134,8 @@ Item { init(); }) } - } else { - pageStack.push(Qt.resolvedUrl("MainPage.qml")) } - PlatformHelper.hideSplashScreen(); + pageStack.push(Qt.resolvedUrl("connection/ConnectingPage.qml")) } function handleCloseEvent(close) { @@ -169,11 +174,19 @@ Item { } } + Connections { + target: engine.connection + onCurrentHostChanged: { + init(); + } + } + Connections { target: engine.jsonRpcClient onConnectedChanged: { print("json client connected changed", engine.jsonRpcClient.connected) if (engine.jsonRpcClient.connected) { + discovery.cacheHost(engine.connection.currentHost) tabSettings.lastConnectedHost = engine.jsonRpcClient.serverUuid } init(); diff --git a/nymea-app/ui/SettingsPage.qml b/nymea-app/ui/SettingsPage.qml index df24ac24..5dcdf8e2 100644 --- a/nymea-app/ui/SettingsPage.qml +++ b/nymea-app/ui/SettingsPage.qml @@ -37,7 +37,7 @@ Page { Label { Layout.fillWidth: true elide: Text.ElideMiddle - text: engine.connection.url + text: engine.connection.currentConnection.url } Button { text: qsTr("Disconnect") diff --git a/nymea-app/ui/connection/ConnectPage.qml b/nymea-app/ui/connection/ConnectPage.qml index 7334f351..55618cc9 100644 --- a/nymea-app/ui/connection/ConnectPage.qml +++ b/nymea-app/ui/connection/ConnectPage.qml @@ -8,12 +8,18 @@ import "../components" Page { id: root - readonly property bool haveHosts: discovery.discoveryModel.count > 0 + readonly property bool haveHosts: hostsProxy.count > 0 Component.onCompleted: { - print("completed connectPage. last connected host:", tabSettings.lastConnectedHost) + print("Ready to connect") if (tabSettings.lastConnectedHost.length > 0) { - discovery.resolveServerUuid(tabSettings.lastConnectedHost) + print("Last connected host was", tabSettings.lastConnectedHost) + var cachedHost = discovery.nymeaHosts.find(tabSettings.lastConnectedHost); + if (cachedHost) { + engine.connection.currentHost = cachedHost + } else { + print("Warning: There is a last connected host but UUID is unknown to discovery...") + } } else { PlatformHelper.hideSplashScreen(); } @@ -30,16 +36,6 @@ Page { // } } - Connections { - target: discovery - onServerUuidResolved: { - print("** resolved", uuid, tabSettings.lastConnectedHost) - if (uuid == tabSettings.lastConnectedHost) { - print("yesss") - connectToHost(url, true); - } - } - } function connectToHost(url, noAnimations) { var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml"), noAnimations ? StackView.Immediate : StackView.PushTransition) @@ -51,12 +47,23 @@ Page { engine.connection.connect(url) } -// NymeaDiscovery { -// id: discovery -// objectName: "discovery" -// awsClient: AWSClient -// discovering: pageStack.currentItem.objectName === "discoveryPage" -// } + function connectToHost2(host, noAnimations) { + var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml"), noAnimations ? StackView.Immediate : StackView.PushTransition) + page.cancel.connect(function() { + engine.connection.disconnect() + pageStack.pop(root, StackView.Immediate); + pageStack.push(discoveryPage) + }) + print("Connecting to host", host) + engine.connection.connect(host) + } + + NymeaHostsFilterModel { + id: hostsProxy + discovery: _discovery + showUnreachableBearers: false + nymeaConnection: engine.connection + } Connections { target: engine.connection @@ -169,7 +176,7 @@ Page { Label { Layout.fillWidth: true 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) : 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 @@ -181,27 +188,27 @@ Page { ListView { Layout.fillWidth: true Layout.fillHeight: true - model: discovery.discoveryModel + model: hostsProxy clip: true delegate: MeaListItemDelegate { - id: discoveryDeviceDelegate + id: nymeaHostDelegate width: parent.width height: app.delegateHeight objectName: "discoveryDelegate" + index - property var discoveryDevice: discovery.discoveryModel.get(index) + property var nymeaHost: discovery.nymeaHosts.get(index) property string defaultConnectionIndex: { var usedConfigIndex = 0; - for (var i = 1; i < discoveryDevice.connections.count; i++) { - var oldConfig = discoveryDevice.connections.get(usedConfigIndex); - var newConfig = discoveryDevice.connections.get(i); + for (var i = 1; i < nymeaHost.connections.count; i++) { + var oldConfig = nymeaHost.connections.get(usedConfigIndex); + var newConfig = nymeaHost.connections.get(i); // Preference of bearerType var bearerPreference = [Connection.BearerTypeEthernet, Connection.BearerTypeWifi, Connection.BearerTypeBluetooth, Connection.BearerTypeCloud] 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) + print(nymeaHost.name, "switching to preferred index", i, "of bearer type", newConfig.bearerType, "from", oldConfig.bearerType, "new prio:", newBearerPriority, "old:", oldBearerPriority) usedConfigIndex = i; continue; } @@ -227,7 +234,7 @@ Page { } iconName: { - switch (discoveryDevice.connections.get(defaultConnectionIndex).bearerType) { + switch (nymeaHost.connections.get(defaultConnectionIndex).bearerType) { case Connection.BearerTypeWifi: return "../images/network-wifi-symbolic.svg"; case Connection.BearerTypeEthernet: @@ -241,21 +248,21 @@ Page { } text: model.name - subText: discoveryDevice.connections.get(defaultConnectionIndex).url + subText: nymeaHost.connections.get(defaultConnectionIndex).url wrapTexts: false prominentSubText: false progressive: false - property bool isSecure: discoveryDevice.connections.get(defaultConnectionIndex).secure - property bool isTrusted: engine.connection.isTrusted(discoveryDeviceDelegate.discoveryDevice.connections.get(defaultConnectionIndex).url) - property bool isOnline: discoveryDevice.connections.get(defaultConnectionIndex).online + property bool isSecure: nymeaHost.connections.get(defaultConnectionIndex).secure + property bool isTrusted: engine.connection.isTrusted(nymeaHostDelegate.nymeaHost.connections.get(defaultConnectionIndex).url) + property bool isOnline: nymeaHost.connections.get(defaultConnectionIndex).online tertiaryIconName: isSecure ? "../images/network-secure.svg" : "" tertiaryIconColor: isTrusted ? app.accentColor : Material.foreground secondaryIconName: !isOnline ? "../images/cloud-error.svg" : "" secondaryIconColor: "red" - swipe.enabled: discoveryDeviceDelegate.discoveryDevice.deviceType === DiscoveryDevice.DeviceTypeNetwork + swipe.enabled: nymeaHostDelegate.nymeaHost.deviceType === NymeaHost.DeviceTypeNetwork onClicked: { - root.connectToHost(discoveryDeviceDelegate.discoveryDevice.connections.get(defaultConnectionIndex).url) + root.connectToHost2(nymeaHostDelegate.nymeaHost) } swipe.right: MouseArea { @@ -268,9 +275,9 @@ Page { name: "../images/info.svg" } onClicked: { - if (model.deviceType === DiscoveryDevice.DeviceTypeNetwork) { + if (model.deviceType === NymeaHost.DeviceTypeNetwork) { swipe.close() - var popup = infoDialog.createObject(app,{discoveryDevice: discovery.discoveryModel.get(index)}) + var popup = infoDialog.createObject(app,{nymeaHost: discovery.nymeaHosts.get(index)}) popup.open() } } @@ -300,14 +307,14 @@ Page { Layout.leftMargin: app.margins Layout.rightMargin: app.margins 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) } Button { Layout.fillWidth: true Layout.leftMargin: app.margins Layout.rightMargin: app.margins - visible: discovery.discoveryModel.count === 0 + visible: discovery.nymeaHosts.count === 0 text: qsTr("Start wireless setup") onClicked: pageStack.push(Qt.resolvedUrl("wifisetup/BluetoothDiscoveryPage.qml"), {nymeaDiscovery: discovery}) } @@ -325,7 +332,7 @@ Page { Layout.leftMargin: app.margins Layout.rightMargin: app.margins Layout.bottomMargin: app.margins - visible: discovery.discoveryModel.count === 0 + visible: discovery.nymeaHosts.count === 0 text: qsTr("Demo mode (online)") onClicked: { root.connectToHost("nymea://nymea.nymea.io:2222") @@ -472,7 +479,7 @@ Page { standardButtons: Dialog.Ok - property var discoveryDevice: null + property var nymeaHost: null header: Item { implicitHeight: headerRow.height + app.margins * 2 @@ -508,7 +515,7 @@ Page { text: "Name:" } Label { - text: dialog.discoveryDevice.name + text: dialog.nymeaHost.name Layout.fillWidth: true elide: Text.ElideRight } @@ -516,7 +523,7 @@ Page { text: "UUID:" } Label { - text: dialog.discoveryDevice.uuid + text: dialog.nymeaHost.uuid Layout.fillWidth: true elide: Text.ElideRight } @@ -524,7 +531,7 @@ Page { text: "Version:" } Label { - text: dialog.discoveryDevice.version + text: dialog.nymeaHost.version Layout.fillWidth: true elide: Text.ElideRight } @@ -544,7 +551,7 @@ Page { id: contentColumn width: parent.width Repeater { - model: dialog.discoveryDevice.connections + model: dialog.nymeaHost.connections delegate: MeaListItemDelegate { Layout.fillWidth: true wrapTexts: false @@ -573,7 +580,7 @@ Page { secondaryIconColor: "red" onClicked: { - root.connectToHost(dialog.discoveryDevice.connections.get(index).url) + root.connectToHost2(dialog.nymeaHost.connections.get(index)) dialog.close() } } diff --git a/nymea-app/ui/connection/ConnectingPage.qml b/nymea-app/ui/connection/ConnectingPage.qml index 2f11739d..ca2132d9 100644 --- a/nymea-app/ui/connection/ConnectingPage.qml +++ b/nymea-app/ui/connection/ConnectingPage.qml @@ -28,7 +28,7 @@ Page { } Label { Layout.fillWidth: true - text: engine.connection.url + text: engine.connection.currentHost.uuid font.pixelSize: app.smallFont wrapMode: Text.WrapAtWordBoundaryOrAnywhere horizontalAlignment: Text.AlignHCenter diff --git a/nymea-app/ui/connection/wifisetup/WirelessSetupPage.qml b/nymea-app/ui/connection/wifisetup/WirelessSetupPage.qml index 321d5b0c..f9900ae6 100644 --- a/nymea-app/ui/connection/wifisetup/WirelessSetupPage.qml +++ b/nymea-app/ui/connection/wifisetup/WirelessSetupPage.qml @@ -52,7 +52,7 @@ Page { } Connections { - target: root.nymeaDiscovery.discoveryModel + target: root.nymeadiscovery.nymeaHosts 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) - for (var i = 0; i < root.nymeaDiscovery.discoveryModel.count; i++) { - for (var j = 0; j < root.nymeaDiscovery.discoveryModel.get(i).connections.count; j++) { - if (root.nymeaDiscovery.discoveryModel.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 + for (var i = 0; i < root.nymeadiscovery.nymeaHosts.count; i++) { + for (var j = 0; j < root.nymeadiscovery.nymeaHosts.get(i).connections.count; j++) { + if (root.nymeadiscovery.nymeaHosts.get(i).connections.get(j).url.toString().indexOf(root.networkManagerController.manager.currentConnection.hostAddress) >= 0) { + connectButton.url = root.nymeadiscovery.nymeaHosts.get(i).connections.get(j).url return; } } - root.nymeaDiscovery.discoveryModel.get(i).connections.countChanged.connect(function() { + root.nymeadiscovery.nymeaHosts.get(i).connections.countChanged.connect(function() { updateConnectButton(); }) }