diff --git a/de_web_plugin.cpp b/de_web_plugin.cpp index b74ca16fb7..8c63d2e504 100644 --- a/de_web_plugin.cpp +++ b/de_web_plugin.cpp @@ -174,8 +174,8 @@ DeRestPluginPrivate::DeRestPluginPrivate(QObject *parent) : webSocketServer = 0; gwScanner = new GatewayScanner(this); - connect(gwScanner, SIGNAL(foundGateway(quint32,quint16,QString,QString)), - this, SLOT(foundGateway(quint32,quint16,QString,QString))); + connect(gwScanner, SIGNAL(foundGateway(QHostAddress,quint16,QString,QString)), + this, SLOT(foundGateway(QHostAddress,quint16,QString,QString))); gwScanner->startScan(); QString dataPath = deCONZ::getStorageLocation(deCONZ::ApplicationsDataLocation); diff --git a/de_web_plugin_private.h b/de_web_plugin_private.h index 0d02f498ae..3ee317fd82 100644 --- a/de_web_plugin_private.h +++ b/de_web_plugin_private.h @@ -895,7 +895,7 @@ public Q_SLOTS: void ntpqFinished(); // gateways - void foundGateway(quint32 ip, quint16 port, const QString &uuid, const QString &name); + void foundGateway(const QHostAddress &host, quint16 port, const QString &uuid, const QString &name); public: void checkRfConnectState(); diff --git a/gateway_scanner.cpp b/gateway_scanner.cpp index 5f1a039660..2b033e7158 100644 --- a/gateway_scanner.cpp +++ b/gateway_scanner.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 dresden elektronik ingenieurtechnik gmbh. + * Copyright (c) 2016-2017 dresden elektronik ingenieurtechnik gmbh. * All rights reserved. * * The software in this package is published under the terms of the BSD @@ -15,7 +15,8 @@ #include #include #include "gateway_scanner.h" -#include "deconz/dbg_trace.h" +#include "deconz.h" +#include "json.h" enum ScanState { @@ -39,6 +40,7 @@ class GatewayScannerPrivate void startScanTimer(int msec, ScanEvent action); void stopTimer(); void queryNextIp(); + void processReply(); GatewayScanner *q; ScanState state; @@ -86,6 +88,18 @@ bool GatewayScanner::isRunning() const return (d->state != StateInit); } +void GatewayScanner::queryGateway(const QString &url) +{ + Q_D(GatewayScanner); + + if (!isRunning() && d->reply == 0) + { + d->reply = d->manager->get(QNetworkRequest(url)); + QObject::connect(d->reply, SIGNAL(error(QNetworkReply::NetworkError)), + d->manager->parent(), SLOT(onError(QNetworkReply::NetworkError))); + } +} + void GatewayScanner::startScan() { Q_D(GatewayScanner); @@ -105,12 +119,72 @@ void GatewayScanner::scanTimerFired() void GatewayScanner::requestFinished(QNetworkReply *reply) { Q_D(GatewayScanner); + if (reply == d->reply) + { + d->processReply(); + } + + if (isRunning()) { d->handleEvent(EventGotReply); } } +void GatewayScannerPrivate::processReply() +{ + if (!reply) + { + return; + } + + QNetworkReply *r = reply; + reply = 0; + r->deleteLater(); + + int code = r->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (code != 200) // not authorized or ok + { + return; + } + + bool ok; + QVariant var = Json::parse(r->readAll(), ok); + if (!ok) + { + return; + } + + QVariantMap map = var.toMap(); + if (map.isEmpty()) + { + return; + } + + if (!map.contains(QLatin1String("bridgeid")) || + !map.contains(QLatin1String("modelid")) || + !map.contains(QLatin1String("name"))) + { + return; + } + + QString name = map["name"].toString(); + //QString modelid = map["modelid"].toString(); + QString bridgeid = map["bridgeid"].toString(); + + QUrl url = r->url(); + + QHostAddress host(url.host()); + if (host.isNull() || name.isEmpty() || bridgeid.isEmpty()) + { + return; + } + + //DBG_Printf(DBG_INFO, "GW: %s %s, %s, %s\n", qPrintable(url.host()), qPrintable(name), qPrintable(modelid), qPrintable(bridgeid)); + q->foundGateway(host, url.port(80), bridgeid, name); +} + void GatewayScanner::onError(QNetworkReply::NetworkError code) { Q_D(GatewayScanner); @@ -184,9 +258,16 @@ void GatewayScannerPrivate::handleEvent(ScanEvent event) { if (state == StateInit) { - initScanner(); - state = StateIdle; - startScanTimer(10, ActionProcess); + if (event == ActionProcess) + { + initScanner(); + state = StateIdle; + startScanTimer(10, ActionProcess); + } + else + { + Q_ASSERT(0); + } } else if (state == StateIdle) { @@ -211,58 +292,6 @@ void GatewayScannerPrivate::handleEvent(ScanEvent event) } else if (event == EventGotReply) { - QNetworkReply *r = reply; - if (reply) - { - reply = 0; - int code = r->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (code == 200) // not authorized or ok - { - QNetworkRequest req = r->request(); - DBG_Printf(DBG_INFO, "reply code %d from %s\n", code, qPrintable(req.url().toString())); - - QString name; - bool isGateway = false; - char buf[256]; - qint64 n = 0; - while ((n = r->readLine(buf, sizeof(buf))) > 0) - { - if (n < 10 || (size_t)n >= sizeof(buf)) - continue; - - buf[n] = '\0'; - - if (!isGateway) - { - if (strstr(buf, ">dresden elektronik<")) - { - isGateway = true; - } - } - else if (strstr(buf, "")) - { - const char *start = strchr(buf, '>') + 1; - const char *end = strstr(start, ""); - if (!end || start == end) - continue; - buf[end - buf] = '\0'; - name = start; - } - else if (strstr(buf, "uuid:")) - { - const char *start = strchr(buf, ':') + 1; - const char *end = strstr(start, ""); - if (!end || start == end) - continue; - buf[end - buf] = '\0'; - q->foundGateway(scanIp, scanPort, start, name); - break; - } - } - } - r->deleteLater(); - } host++; startScanTimer(1, ActionProcess); } @@ -314,7 +343,7 @@ void GatewayScannerPrivate::queryNextIp() } QString url; - url.sprintf("http://%u.%u.%u.%u:%u/description.xml", + url.sprintf("http://%u.%u.%u.%u:%u/api/config", ((scanIp >> 24) & 0xff), ((scanIp >> 16) & 0xff), ((scanIp >> 8) & 0xff), diff --git a/gateway_scanner.h b/gateway_scanner.h index e6b12ebfe7..a584e97776 100644 --- a/gateway_scanner.h +++ b/gateway_scanner.h @@ -14,9 +14,10 @@ class GatewayScanner : public QObject explicit GatewayScanner(QObject *parent = 0); ~GatewayScanner(); bool isRunning() const; + void queryGateway(const QString &url); Q_SIGNALS: - void foundGateway(quint32 ip, quint16 port, const QString &uuid, const QString &name); + void foundGateway(const QHostAddress &host, quint16 port, const QString &uuid, const QString &name); public Q_SLOTS: void startScan(); diff --git a/rest_configuration.cpp b/rest_configuration.cpp index b9eccfbb8d..1aca7210f1 100644 --- a/rest_configuration.cpp +++ b/rest_configuration.cpp @@ -23,6 +23,7 @@ #include #include #include +#include "gateway.h" #ifdef ARCH_ARM #include #include @@ -955,6 +956,35 @@ int DeRestPluginPrivate::getBasicConfig(const ApiRequest &req, ApiResponse &rsp) } } basicConfigToMap(rsp.map); + + // add more details if this was requested from discover page + // this should speedup multi-gateway discovery + if (!gateways.empty()) + { + // restrict info TODO more limited "Origin" + QString referer = req.hdr.value(QLatin1String("Referer")); + if (referer.contains(QLatin1String("js/scanner-worker.js"))) + { + QVariantList ls; + for (const Gateway *gw : gateways) + { + DBG_Assert(gw != 0); + if (gw) + { + QVariantMap g; + g["host"] = gw->address().toString(); + g["port"] = gw->port(); + ls.push_back(g); + } + } + + if (!ls.empty()) + { + rsp.map["gateways"] = ls; + } + } + } + rsp.httpStatus = HttpStatusOk; rsp.etag = gwConfigEtag; return REQ_READY_SEND; diff --git a/rest_gateways.cpp b/rest_gateways.cpp index 4ef95c8eda..319626ee98 100644 --- a/rest_gateways.cpp +++ b/rest_gateways.cpp @@ -407,7 +407,7 @@ void DeRestPluginPrivate::gatewayToMap(const ApiRequest &req, const Gateway *gw, } } -void DeRestPluginPrivate::foundGateway(quint32 ip, quint16 port, const QString &uuid, const QString &name) +void DeRestPluginPrivate::foundGateway(const QHostAddress &host, quint16 port, const QString &uuid, const QString &name) { if (uuid.isEmpty()) { @@ -421,9 +421,9 @@ void DeRestPluginPrivate::foundGateway(quint32 ip, quint16 port, const QString & if (gw && gw->uuid() == uuid) { - if (gw->address().toIPv4Address() != ip || gw->port() != port) + if (gw->address().toIPv4Address() != host.toIPv4Address() || gw->port() != port) { - gw->setAddress(QHostAddress(ip)); + gw->setAddress(host); gw->setPort(port); } @@ -446,13 +446,14 @@ void DeRestPluginPrivate::foundGateway(quint32 ip, quint16 port, const QString & QString gwApikey = gwUuid.left(10); Gateway *gw = new Gateway(this); - gw->setAddress(QHostAddress(ip)); + gw->setAddress(host); gw->setPort(port); gw->setUuid(uuid); gw->setName(name); gw->setApiKey(gwApikey); DBG_Printf(DBG_INFO, "found gateway %s:%u\n", qPrintable(gw->address().toString()), port); gateways.push_back(gw); + updateEtag(gwConfigEtag); } diff --git a/upnp.cpp b/upnp.cpp index 7cbaefc38f..d828d1cc95 100644 --- a/upnp.cpp +++ b/upnp.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 dresden elektronik ingenieurtechnik gmbh. + * Copyright (c) 2016-2017 dresden elektronik ingenieurtechnik gmbh. * All rights reserved. * * The software in this package is published under the terms of the BSD @@ -16,6 +16,7 @@ #include #include "de_web_plugin.h" #include "de_web_plugin_private.h" +#include "gateway_scanner.h" /*! Inits the UPnP discorvery. */ void DeRestPluginPrivate::initUpnpDiscovery() @@ -147,25 +148,48 @@ void DeRestPluginPrivate::upnpReadyRead() QTextStream stream(datagram); QString searchTarget; + QString location; + + if (DBG_IsEnabled(DBG_HTTP)) + { + DBG_Printf(DBG_HTTP, "%s\n", qPrintable(datagram)); + } + while (!stream.atEnd()) { QString line = stream.readLine(); - if (!line.startsWith(QLatin1String("ST:"))) + if (!searchTarget.isEmpty()) { - continue; + break; } - if (line.contains(QLatin1String("ssdp:all")) || - line.contains(QLatin1String("device:basic")) || - line.contains(QLatin1String("upnp:rootdevice"))) + if (line.startsWith(QLatin1String("LOCATION:"))) + { + location = line; + } + else if (line.startsWith(QLatin1String("GWID.phoscon.de"))) + { + searchTarget = line; + } + else if (line.startsWith(QLatin1String("hue-bridgeid"))) { searchTarget = line; - break; + } + else if (line.startsWith(QLatin1String("ST:"))) + { + if (line.contains(QLatin1String("ssdp:all")) || + line.contains(QLatin1String("device:basic")) || + line.contains(QLatin1String("upnp:rootdevice"))) + { + searchTarget = line; + } } } - if (datagram.startsWith("M-SEARCH *") && !searchTarget.isEmpty()) + if (searchTarget.isEmpty()) + {} + else if (datagram.startsWith("M-SEARCH *")) { DBG_Printf(DBG_HTTP, "UPNP %s:%u\n%s\n", qPrintable(host.toString()), port, datagram.data()); @@ -190,5 +214,38 @@ void DeRestPluginPrivate::upnpReadyRead() DBG_Printf(DBG_ERROR, "UDP send error %s\n", qPrintable(udpSockOut->errorString())); } } + else if (datagram.startsWith("NOTIFY *") && !location.isEmpty()) + { + // phoscon gateway or hue bridge + QStringList ls = searchTarget.split(' '); + if (ls.size() != 2) + { + continue; + } + + if (ls[1] == gwBridgeId) + { + continue; // self + } + + ls = location.split(' '); + if (ls.size() != 2 || !ls[1].startsWith(QLatin1String("http://"))) + { + continue; + } + + // http://192.168.14.103:80/description.xml + location = ls[1]; + //location.remove(QLatin1String("http://")); + int idx = location.indexOf('/'); + if (idx == -1) + { + continue; + } + location = location.left(idx); + location += QLatin1String("/api/config"); + + gwScanner->queryGateway(location); + } } }