From fd62c1a3220773d2a3d5718a985caea3bda5e00f Mon Sep 17 00:00:00 2001 From: Manuel Pietschmann Date: Sun, 11 Sep 2016 11:00:56 +0200 Subject: [PATCH] Start work on cross gateway communication (wip) --- de_web.pro | 37 +++--- de_web_plugin.cpp | 16 ++- de_web_plugin_private.h | 13 ++ gateway.cpp | 251 +++++++++++++++++++++++++++++++++++ gateway.h | 37 ++++++ gateway_scanner.cpp | 287 ++++++++++++++++++++++++++++++++++++++++ gateway_scanner.h | 34 +++++ rest_gateways.cpp | 94 +++++++++++++ 8 files changed, 752 insertions(+), 17 deletions(-) create mode 100644 gateway.cpp create mode 100644 gateway.h create mode 100644 gateway_scanner.cpp create mode 100644 gateway_scanner.h create mode 100644 rest_gateways.cpp diff --git a/de_web.pro b/de_web.pro index 603ddadff7..532117a638 100644 --- a/de_web.pro +++ b/de_web.pro @@ -73,55 +73,60 @@ else { QMAKE_CXXFLAGS += -Wno-attributes HEADERS = bindings.h \ - de_web_plugin.h \ - de_web_widget.h \ connectivity.h \ - json.h \ colorspace.h \ - sqlite3.h \ + de_web_plugin.h \ de_web_plugin_private.h \ - rest_node_base.h \ - light_node.h \ + de_web_widget.h \ + gateway.h \ + gateway_scanner.h \ group.h \ group_info.h \ + json.h \ + light_node.h \ + sqlite3.h \ + rest_node_base.h \ rule.h \ scene.h \ sensor.h SOURCES = authentification.cpp \ + atmel_wsndemo_sensor.cpp \ bindings.cpp \ change_channel.cpp \ connectivity.cpp \ + colorspace.cpp \ database.cpp \ discovery.cpp \ de_web_plugin.cpp \ de_web_widget.cpp \ de_otau.cpp \ firmware_update.cpp \ + gateway.cpp \ + gateway_scanner.cpp \ + group.cpp \ + group_info.cpp \ + gw_uuid.cpp \ json.cpp \ - colorspace.cpp \ + light_node.cpp \ sqlite3.c \ - rest_lights.cpp \ rest_configuration.cpp \ + rest_gateways.cpp \ rest_groups.cpp \ + rest_lights.cpp \ + rest_node_base.cpp \ rest_rules.cpp \ rest_sensors.cpp \ rest_schedules.cpp \ rest_touchlink.cpp \ rule.cpp \ upnp.cpp \ - zcl_tasks.cpp \ - gw_uuid.cpp \ permitJoin.cpp \ - rest_node_base.cpp \ - light_node.cpp \ - group.cpp \ - group_info.cpp \ scene.cpp \ sensor.cpp \ - atmel_wsndemo_sensor.cpp \ reset_device.cpp \ - rest_userparameter.cpp + rest_userparameter.cpp \ + zcl_tasks.cpp win32:DESTDIR = ../../debug/plugins # TODO adjust unix:DESTDIR = .. diff --git a/de_web_plugin.cpp b/de_web_plugin.cpp index baafa0cb50..59964ae6b0 100644 --- a/de_web_plugin.cpp +++ b/de_web_plugin.cpp @@ -29,6 +29,7 @@ #include "de_web_plugin.h" #include "de_web_plugin_private.h" #include "de_web_widget.h" +#include "gateway_scanner.h" #include "json.h" const char *HttpStatusOk = "200 OK"; // OK @@ -89,6 +90,12 @@ DeRestPluginPrivate::DeRestPluginPrivate(QObject *parent) : connect(databaseTimer, SIGNAL(timeout()), this, SLOT(saveDatabaseTimerFired())); + + gwScanner = new GatewayScanner(this); + connect(gwScanner, SIGNAL(foundGateway(quint32,quint16,QString,QString)), + this, SLOT(foundGateway(quint32,quint16,QString,QString))); + gwScanner->startScan(); + db = 0; saveDatabaseItems = 0; sqliteDatabaseName = deCONZ::getStorageLocation(deCONZ::ApplicationsDataLocation) + QLatin1String("/zll.db"); @@ -6913,6 +6920,7 @@ bool DeRestPlugin::isHttpTarget(const QHttpRequestHeader &hdr) (ls[2] == QLatin1String("touchlink")) || (ls[2] == QLatin1String("rules")) || (ls[2] == QLatin1String("userparameter")) || + (ls[2] == QLatin1String("gateways")) || (hdr.path().at(4) != '/') /* Bug in some clients */) { return true; @@ -7010,8 +7018,10 @@ int DeRestPlugin::handleHttpRequest(const QHttpRequestHeader &hdr, QTcpSocket *s stream << "Access-Control-Allow-Credentials: true\r\n"; stream << "Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE\r\n"; stream << "Access-Control-Allow-Headers: Content-Type\r\n"; - stream << "Content-type: text/html\r\n"; + stream << "Content-Type: text/html\r\n"; stream << "Content-Length: 0\r\n"; + stream << "Gateway-Name: " << d->gwName << "\r\n"; + stream << "Gateway-Uuid: " << d->gwUuid << "\r\n"; stream << "\r\n"; req.sock->flush(); return 0; @@ -7083,6 +7093,10 @@ int DeRestPlugin::handleHttpRequest(const QHttpRequestHeader &hdr, QTcpSocket *s { ret = d->handleUserparameterApi(req, rsp); } + else if (path[2] == QLatin1String("gateways")) + { + ret = d->handleGatewaysApi(req, rsp); + } } if (ret == REQ_NOT_HANDLED) diff --git a/de_web_plugin_private.h b/de_web_plugin_private.h index 6f210d273d..f5804817fc 100644 --- a/de_web_plugin_private.h +++ b/de_web_plugin_private.h @@ -239,6 +239,8 @@ extern const char *HttpContentJPG; extern const char *HttpContentSVG; // Forward declarations +class Gateway; +class GatewayScanner; class QUdpSocket; class QTcpSocket; class DeRestPlugin; @@ -497,6 +499,10 @@ class DeRestPluginPrivate : public QObject bool checkApikeyAuthentification(const ApiRequest &req, ApiResponse &rsp); QString encryptString(const QString &str); + // REST API gateways + int handleGatewaysApi(const ApiRequest &req, ApiResponse &rsp); + int getGateways(const ApiRequest &req, ApiResponse &rsp); + // REST API configuration int handleConfigurationApi(const ApiRequest &req, ApiResponse &rsp); int createUser(const ApiRequest &req, ApiResponse &rsp); @@ -732,6 +738,9 @@ public Q_SLOTS: void checkWifiState(); void restoreWifiState(); + // gateways + void foundGateway(quint32 ip, quint16 port, const QString &uuid, const QString &name); + public: void checkRfConnectState(); bool isInNetwork(); @@ -868,6 +877,10 @@ public Q_SLOTS: QTimer *databaseTimer; QString emptyString; + // gateways + std::vector gateways; + GatewayScanner *gwScanner; + // authentification QTime apiAuthSaveDatabaseTime; std::vector apiAuths; diff --git a/gateway.cpp b/gateway.cpp new file mode 100644 index 0000000000..e8885f27d6 --- /dev/null +++ b/gateway.cpp @@ -0,0 +1,251 @@ +#include +#include +#include +#include +#include +#include "gateway.h" + +enum GW_State +{ + GW_StateOffline, + GW_StateNotAuthorized, + GW_StateConnected +}; + +enum GW_Event +{ + ActionProcess, + EventTimeout, + EventResponse +}; + +class GatewayPrivate +{ +public: + void startTimer(int msec, GW_Event event); + void handleEvent(GW_Event event); + void handleEventStateOffline(GW_Event event); + void handleEventStateNotAuthorized(GW_Event event); + void handleEventStateConnected(GW_Event event); + + GW_State state; + QString apikey; + QString name; + QString uuid; + QHostAddress address; + quint16 port; + QTimer *timer; + GW_Event timerAction; + QNetworkAccessManager *manager; + QNetworkReply *reply; + QTime tLastReply; +}; + +Gateway::Gateway(QObject *parent) : + QObject(parent), + d_ptr(new GatewayPrivate) +{ + Q_D(Gateway); + d->state = GW_StateOffline; + d->reply = 0; + d->manager = new QNetworkAccessManager(this); + d->timer = new QTimer(this); + d->timer->setSingleShot(true); + connect(d->timer, SIGNAL(timeout()), this, SLOT(timerFired())); + + d->apikey = "adasd90892msd"; + + d->startTimer(5000, ActionProcess); +} + +Gateway::~Gateway() +{ + Q_ASSERT(d_ptr != 0); + + if (d_ptr) + { + delete d_ptr; + d_ptr = 0; + } +} + +void Gateway::setAddress(const QHostAddress &address) +{ + Q_D(Gateway); + if (d->address != address) + { + d->address = address; + } +} + +const QString &Gateway::name() const +{ + Q_D(const Gateway); + return d->name; +} + +void Gateway::setName(const QString &name) +{ + Q_D(Gateway); + if (d->name != name) + { + d->name = name; + } +} + +const QString &Gateway::uuid() const +{ + Q_D(const Gateway); + return d->uuid; +} + +void Gateway::setUuid(const QString &uuid) +{ + Q_D(Gateway); + if (d->uuid != uuid) + { + d->uuid = uuid; + } +} + +const QHostAddress &Gateway::address() const +{ + Q_D(const Gateway); + return d->address; +} + +quint16 Gateway::port() const +{ + Q_D(const Gateway); + return d->port; +} + +void Gateway::setPort(quint16 port) +{ + Q_D(Gateway); + if (d->port != port) + { + d->port = port; + } +} + +void Gateway::timerFired() +{ + Q_D(Gateway); + d->handleEvent(d->timerAction); +} + +void Gateway::finished(QNetworkReply *reply) +{ + Q_D(Gateway); + if (d->reply == reply) + { + d->handleEvent(EventResponse); + } +} + +void Gateway::error(QNetworkReply::NetworkError) +{ + Q_D(Gateway); + if (d->reply && sender() == d->reply) + { + d->handleEvent(EventResponse); + } +} + + +void GatewayPrivate::startTimer(int msec, GW_Event event) +{ + timerAction = event; + timer->start(msec); +} + +void GatewayPrivate::handleEvent(GW_Event event) +{ + if (state == GW_StateOffline) { handleEventStateOffline(event); } + else if (state == GW_StateNotAuthorized) { handleEventStateNotAuthorized(event); } + else if (state == GW_StateConnected) { handleEventStateConnected(event); } + else + { + Q_ASSERT(0); + } +} + +void GatewayPrivate::handleEventStateOffline(GW_Event event) +{ + if (event == ActionProcess) + { + if (port == 0 || address.isNull()) + { + // need parameters + startTimer(1000, ActionProcess); + return; + } + + QString url; + url.sprintf("http://%s:%u/api/%s/config", + qPrintable(address.toString()), port, qPrintable(apikey)); + + reply = manager->get(QNetworkRequest(url)); + QObject::connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + manager->parent(), SLOT(error(QNetworkReply::NetworkError))); + + + startTimer(100, EventTimeout); + } + else if (event == EventResponse) + { + timer->stop(); + tLastReply.start(); + + QNetworkReply *r = reply; + if (reply) + { + reply = 0; + int code = r->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (code == 403 || code == 200) // not authorized or ok + { + QNetworkRequest req = r->request(); + QByteArray data = r->readAll(); + DBG_Printf(DBG_INFO, "GW reply code %d from %s\n%s\n", code, qPrintable(req.url().toString()), qPrintable(data)); + } + r->deleteLater(); + + if (code == 403) + { + state = GW_StateNotAuthorized; + startTimer(10, ActionProcess); + } + else if (code == 200) + { + state = GW_StateConnected; + startTimer(10, ActionProcess); + } + } + } + else if (event == EventTimeout) + { + if (reply) + { + QNetworkReply *r = reply; + reply = 0; + if (r->isRunning()) + { + r->abort(); + } + r->deleteLater(); + } + startTimer(10000, ActionProcess); + } +} + +void GatewayPrivate::handleEventStateNotAuthorized(GW_Event event) +{ + +} + +void GatewayPrivate::handleEventStateConnected(GW_Event event) +{ + +} diff --git a/gateway.h b/gateway.h new file mode 100644 index 0000000000..82f7b7f099 --- /dev/null +++ b/gateway.h @@ -0,0 +1,37 @@ +#ifndef GATEWAY_H +#define GATEWAY_H + +#include +#include +#include + +class GatewayPrivate; +class Gateway : public QObject +{ + Q_OBJECT +public: + explicit Gateway(QObject *parent = 0); + ~Gateway(); + + const QString &name() const; + void setName(const QString &name); + const QString &uuid() const; + void setUuid(const QString &uuid); + const QHostAddress &address() const; + void setAddress(const QHostAddress &address); + quint16 port() const; + void setPort(quint16 port); + +signals: + +private Q_SLOTS: + void timerFired(); + void finished(QNetworkReply *reply); + void error(QNetworkReply::NetworkError); + +private: + Q_DECLARE_PRIVATE(Gateway) + GatewayPrivate *d_ptr; +}; + +#endif // GATEWAY_H diff --git a/gateway_scanner.cpp b/gateway_scanner.cpp new file mode 100644 index 0000000000..75c681ed7c --- /dev/null +++ b/gateway_scanner.cpp @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2016 dresden elektronik ingenieurtechnik gmbh. + * All rights reserved. + * + * The software in this package is published under the terms of the BSD + * style license a copy of which has been included with this distribution in + * the LICENSE.txt file. + * + */ + +#include +#include +#include +#include +#include +#include +#include "gateway_scanner.h" +#include "deconz/dbg_trace.h" + +enum ScanState +{ + StateInit, + StateIdle, + StateRunning, +}; + +enum ScanEvent +{ + ActionProcess, + EventTimeout, + EventGotReply +}; + +class GatewayScannerPrivate +{ +public: + void initScanner(); + void handleEvent(ScanEvent event); + void startScanTimer(int msec, ScanEvent action); + void stopTimer(); + void queryNextIp(); + + GatewayScanner *q; + ScanState state; + QNetworkAccessManager *manager; + QNetworkReply *reply; + QTimer *timer; + ScanEvent timerAction; + std::vector interfaces; + quint32 scanIp; + quint16 scanPort; + int scanIteration; + quint32 host; + size_t interfaceIter; +}; + +GatewayScanner::GatewayScanner(QObject *parent) : + QObject(parent), + d_ptr(new GatewayScannerPrivate) +{ + Q_D(GatewayScanner); + d->q = this; + d->scanIteration = 0; + d->state = StateInit; + d->manager = new QNetworkAccessManager(this); + d->timer = new QTimer(this); + d->timer->setSingleShot(true); + connect(d->timer, SIGNAL(timeout()), this, SLOT(scanTimerFired())); +} + +GatewayScanner::~GatewayScanner() +{ + Q_ASSERT(d_ptr != 0); + if (d_ptr) + { + delete d_ptr; + d_ptr = 0; + } +} + +bool GatewayScanner::isRunning() const +{ + Q_D(const GatewayScanner); + + return (d->state != StateInit); +} + +void GatewayScanner::startScan() +{ + Q_D(GatewayScanner); + + if (d->state == StateInit) + { + d->startScanTimer(1, ActionProcess); + } +} + +void GatewayScanner::scanTimerFired() +{ + Q_D(GatewayScanner); + d->handleEvent(d->timerAction); +} + +void GatewayScanner::requestFinished(QNetworkReply *reply) +{ + Q_D(GatewayScanner); + if (reply == d->reply) + { + d->handleEvent(EventGotReply); + } +} + +void GatewayScanner::onError(QNetworkReply::NetworkError code) +{ + Q_D(GatewayScanner); + Q_UNUSED(code); + + if (!d->timer->isActive()) + { + // must be in timeout window + return; + } + + if (d->reply && sender() == d->reply) + { + //DBG_Printf(DBG_INFO, "reply err: %d\n", code); + d->timer->stop(); + d->handleEvent(EventGotReply); + } +} + +void GatewayScannerPrivate::initScanner() +{ + QList ifaces = QNetworkInterface::allInterfaces(); + + QList::Iterator ifi = ifaces.begin(); + QList::Iterator ifend = ifaces.end(); + + for (; ifi != ifend; ++ifi) + { + QString name = ifi->humanReadableName(); + + // filter + if (name.contains("vm", Qt::CaseInsensitive) || + name.contains("virtual", Qt::CaseInsensitive) || + name.contains("loop", Qt::CaseInsensitive)) + { + continue; + } + + QList addr = ifi->addressEntries(); + + QList::Iterator i = addr.begin(); + QList::Iterator end = addr.end(); + + for (; i != end; ++i) + { + QHostAddress a = i->ip(); + + if (a.protocol() == QAbstractSocket::IPv4Protocol) + { + quint32 ipv4 = a.toIPv4Address(); + if (std::find(interfaces.begin(), interfaces.end(), ipv4) == interfaces.end()) + { + interfaces.push_back(ipv4); + } + } + } + } + + scanIteration++; + interfaceIter = 0; + host = 0; +} + +void GatewayScannerPrivate::handleEvent(ScanEvent event) +{ + if (state == StateInit) + { + initScanner(); + state = StateIdle; + startScanTimer(10, ActionProcess); + } + else if (state == StateIdle) + { + if (event == ActionProcess) + { + queryNextIp(); + } + else if (event == EventTimeout) + { + QNetworkReply *r = reply; + if (reply) + { + reply = 0; + if (r->isRunning()) + { + r->abort(); + } + r->deleteLater(); + } + host++; + startScanTimer(1, ActionProcess); + } + else if (event == EventGotReply) + { + QNetworkReply *r = reply; + if (reply) + { + reply = 0; + int code = r->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (code == 403 || code == 200) // not authorized or ok + { + QNetworkRequest req = r->request(); + QByteArray data = r->readAll(); + DBG_Printf(DBG_INFO, "reply code %d from %s\n%s\n", code, qPrintable(req.url().toString()), qPrintable(data)); + + QString uuid; + QString name; + q->foundGateway(scanIp, scanPort, uuid, name); + + } + r->deleteLater(); + } + host++; + startScanTimer(1, ActionProcess); + } + else + { + Q_ASSERT(0); + } + } + else + { + Q_ASSERT(0); + } +} + +void GatewayScannerPrivate::startScanTimer(int msec, ScanEvent action) +{ + timerAction = action; + timer->stop(); + timer->start(msec); +} + +void GatewayScannerPrivate::stopTimer() +{ + timer->stop(); +} + +void GatewayScannerPrivate::queryNextIp() +{ + if (!interfaces.empty() && host > 255) + { + interfaces.pop_back(); + host = 0; + } + + if (interfaces.empty()) + { + state = StateInit; + DBG_Printf(DBG_INFO, "scan finished\n"); + return; + } + + scanIp = interfaces.back(); + scanPort = 80; + QString url; + QString apikey = "adasd"; + url.sprintf("http://%u.%u.%u.%u:%u/api/%s/config", + ((scanIp >> 24) & 0xff), + ((scanIp >> 16) & 0xff), + ((scanIp >> 8) & 0xff), + host & 0xff, scanPort, qPrintable(apikey)); + + scanIp &= 0xffffff00ull; + scanIp |= host & 0xff; + + //DBG_Printf(DBG_INFO, "scan %s\n", qPrintable(url)); + reply = manager->get(QNetworkRequest(url)); + QObject::connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + manager->parent(), SLOT(onError(QNetworkReply::NetworkError))); + + + startScanTimer(100, EventTimeout); +} diff --git a/gateway_scanner.h b/gateway_scanner.h new file mode 100644 index 0000000000..e6b12ebfe7 --- /dev/null +++ b/gateway_scanner.h @@ -0,0 +1,34 @@ +#ifndef GATEWAYSCANNER_H +#define GATEWAYSCANNER_H + +#include +#include +#include + +class GatewayScannerPrivate; + +class GatewayScanner : public QObject +{ + Q_OBJECT +public: + explicit GatewayScanner(QObject *parent = 0); + ~GatewayScanner(); + bool isRunning() const; + +Q_SIGNALS: + void foundGateway(quint32 ip, quint16 port, const QString &uuid, const QString &name); + +public Q_SLOTS: + void startScan(); + +private Q_SLOTS: + void scanTimerFired(); + void requestFinished(QNetworkReply *reply); + void onError(QNetworkReply::NetworkError code); + +private: + Q_DECLARE_PRIVATE(GatewayScanner) + GatewayScannerPrivate *d_ptr; +}; + +#endif // GATEWAYSCANNER_H diff --git a/rest_gateways.cpp b/rest_gateways.cpp new file mode 100644 index 0000000000..40dc490213 --- /dev/null +++ b/rest_gateways.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016 dresden elektronik ingenieurtechnik gmbh. + * All rights reserved. + * + * The software in this package is published under the terms of the BSD + * style license a copy of which has been included with this distribution in + * the LICENSE.txt file. + * + */ + +#include +#include +#include +#include +#include +#include "de_web_plugin.h" +#include "de_web_plugin_private.h" +#include "gateway.h" +#include "gateway_scanner.h" +#include "json.h" +#include + +/*! Gateways REST API broker. + \param req - request data + \param rsp - response data + \return REQ_READY_SEND + REQ_NOT_HANDLED + */ +int DeRestPluginPrivate::handleGatewaysApi(const ApiRequest &req, ApiResponse &rsp) +{ + if (req.path[2] != QLatin1String("gateways")) + { + return REQ_NOT_HANDLED; + } + + // GET /api//gateways + if ((req.path.size() == 3) && (req.hdr.method() == QLatin1String("GET"))) + { + return getGateways(req, rsp); + } + + return REQ_NOT_HANDLED; +} + +int DeRestPluginPrivate::getGateways(const ApiRequest &req, ApiResponse &rsp) +{ + Q_UNUSED(req); + rsp.httpStatus = HttpStatusOk; + rsp.str = "{}"; + + for (size_t i = 0; i < gateways.size(); i++) + { + Gateway *gw = gateways[i]; + Q_ASSERT(gw); + QVariantMap g; + if (!gw->uuid().isEmpty()) + { + g[QLatin1String("uuid")] = gw->uuid(); + } + if (!gw->name().isEmpty()) + { + g[QLatin1String("name")] = gw->name(); + } + g[QLatin1String("ip")] = gw->address().toString(); + g[QLatin1String("port")] = (double)gw->port(); + rsp.list.push_back(g); + } + + return REQ_READY_SEND; +} + +void DeRestPluginPrivate::foundGateway(quint32 ip, quint16 port, const QString &uuid, const QString &name) +{ + + for (size_t i = 0; i < gateways.size(); i++) + { + Gateway *gw = gateways[i]; + Q_ASSERT(gw); + + if (gw->address().toIPv4Address() == ip && gw->port() == port) + return; // already known + + if (!uuid.isEmpty() && gw->uuid() == uuid) + return; // already known + } + + Gateway *gw = new Gateway(this); + gw->setAddress(QHostAddress(ip)); + gw->setPort(port); + gw->setUuid(uuid); + gw->setName(name); + DBG_Printf(DBG_INFO, "found gateway %s:%u\n", qPrintable(gw->address().toString()), port); + gateways.push_back(gw); +}