From e9e18f227f721a2374762b301c3e582e9d1e91d5 Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 <0xFEEDC0DE64@gmail.com> Date: Sat, 22 Sep 2018 07:24:51 +0200 Subject: [PATCH] Implemented WifiLamp server for lamps --- wifilampapplication.cpp | 210 +++++++++++++++++++++++++++++++++++++++- wifilampapplication.h | 29 +++++- wifilampclient.cpp | 104 ++++++++++++++++++++ wifilampclient.h | 42 ++++++++ wifilampplugin.cpp | 4 +- wifilampplugin.h | 2 +- wifilampplugin.pro | 8 +- 7 files changed, 388 insertions(+), 11 deletions(-) create mode 100644 wifilampclient.cpp create mode 100644 wifilampclient.h diff --git a/wifilampapplication.cpp b/wifilampapplication.cpp index 0b5ea13..0c033b9 100644 --- a/wifilampapplication.cpp +++ b/wifilampapplication.cpp @@ -1,24 +1,226 @@ #include "wifilampapplication.h" +#include +#include +#include +#include + +#include "utils/netutils.h" + +#include "webserver.h" #include "httprequest.h" #include "httpresponse.h" #include "httpclientconnection.h" -WifiLampApplication::WifiLampApplication(const QJsonObject &config, QObject *parent) : - WebApplication(parent) -{ +#include "wifilampclient.h" +WifiLampApplication::WifiLampApplication(const QJsonObject &config, WebServer &webServer) : + WebApplication(&webServer), m_webServer(webServer) +{ + if(!config.contains(QStringLiteral("controlHostAddress"))) + throw std::runtime_error("listener does not contain controlHostAddress"); + + const auto hostAddressVal = config.value(QStringLiteral("controlHostAddress")); + if(!hostAddressVal.isString()) + throw std::runtime_error("listener hostAddress is not a string"); + + m_hostAddress = parseHostAddress(hostAddressVal.toString()); + + if(!config.contains(QStringLiteral("controlPort"))) + throw std::runtime_error("listener does not contain controlPort"); + + const auto portVal = config.value(QStringLiteral("controlPort")); + if(!portVal.isDouble()) + throw std::runtime_error("listener port is not a number"); + + m_port = portVal.toInt(); + + m_tcpServer = new QTcpServer(this); } void WifiLampApplication::start() { + if(!m_tcpServer->listen(m_hostAddress, m_port)) + throw std::runtime_error(QString("Could not start listening on %0:%1 because %2") + .arg(m_hostAddress.toString()).arg(m_port).arg(m_tcpServer->errorString()).toStdString()); + connect(m_tcpServer, &QTcpServer::acceptError, this, &WifiLampApplication::acceptError); + connect(m_tcpServer, &QTcpServer::newConnection, this, &WifiLampApplication::newConnection); } void WifiLampApplication::handleRequest(HttpClientConnection *connection, const HttpRequest &request) { + if(!request.path.startsWith('/')) + { + HttpResponse response; + response.protocol = request.protocol; + response.statusCode = HttpResponse::StatusCode::BadRequest; + response.headers.insert(QStringLiteral("Server"), QStringLiteral("Hatschi Server 1.0")); + response.headers.insert(QStringLiteral("Content-Type"), QStringLiteral("text/html")); + + connection->sendResponse(response, tr("Path does not start with /")); + return; + } + + if(request.path == QStringLiteral("/")) + { + handleRoot(connection, request); + } + else if(request.path == QStringLiteral("/refresh")) + { + for(auto client : m_clients) + client->requestStatus(); + redirectRoot(connection, request); + } + else if(request.path.startsWith("/devices/")) + { + auto parts = request.path.split('/'); + if(parts.count() != 4) + { + handle404(connection, request); + return; + } + + WifiLampClient *client; + + { + auto iter = std::find_if(m_clients.constBegin(), m_clients.constEnd(), + [&parts](auto client){ return clientId(client) == parts.at(2); }); + + if(iter == m_clients.constEnd()) + { + handle404(connection, request); + return; + } + + client = *iter; + } + + static const QHash > actions { + { QStringLiteral("toggle"), [](auto client){ client->toggle(); } }, + { QStringLiteral("on"), [](auto client){ client->on(); } }, + { QStringLiteral("off"), [](auto client){ client->off(); } }, + { QStringLiteral("refresh"), [](auto client){ client->requestStatus(); } }, + { QStringLiteral("reboot"), [](auto client){ client->reboot(); } }, + { QStringLiteral("delete"), [](auto client){ client->deleteLater(); } } + }; + + { + auto iter = actions.find(parts.at(3)); + if(iter == actions.constEnd()) + handle404(connection, request); + else + { + (*iter)(client); + redirectRoot(connection, request); + } + } + } + else + { + handle404(connection, request); + } +} + +const QSet &WifiLampApplication::clients() const +{ + return m_clients; +} + +void WifiLampApplication::acceptError(QAbstractSocket::SocketError socketError) +{ + qCritical() << socketError; +} + +void WifiLampApplication::newConnection() +{ + auto socket = m_tcpServer->nextPendingConnection(); + if(!socket) + { + qWarning() << "null socket received"; + return; + } + + auto client = new WifiLampClient(*socket, *this); + m_clients.insert(client); + connect(client, &QObject::destroyed, this, [this, client](){ + m_clients.remove(client); + }); +} + +void WifiLampApplication::handleRoot(HttpClientConnection *connection, const HttpRequest &request) +{ + QString output = "

Lampen-Steuerung

"; + + output.append("Alle aktualisieren"); + + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + + for(auto client : m_clients) + { + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + } + + output.append(""); + output.append("
IP-AdressNameStatusActions
" % clientId(client, true).toHtmlEscaped() % "" % client->name().toHtmlEscaped() % "" % client->status().toHtmlEscaped() % ""); + output.append("" % tr("Toggle") % " "); + output.append("" % tr("On") % " "); + output.append("" % tr("Off") % " "); + output.append("" % tr("Refresh") % " "); + output.append("" % tr("Reboot") % " "); + output.append("" % tr("Delete") % " "); + output.append("
"); + HttpResponse response; response.protocol = request.protocol; response.statusCode = HttpResponse::StatusCode::OK; - connection->sendResponse(response, "Hello from WifiLampApplication: " + request.path); + response.headers.insert(QStringLiteral("Server"), QStringLiteral("Hatschi Server 1.0")); + response.headers.insert(QStringLiteral("Content-Type"), QStringLiteral("text/html")); + + connection->sendResponse(response, output); +} + +void WifiLampApplication::redirectRoot(HttpClientConnection *connection, const HttpRequest &request) +{ + HttpResponse response; + response.protocol = request.protocol; + response.statusCode = HttpResponse::StatusCode::Found; + + response.headers.insert(QStringLiteral("Server"), QStringLiteral("Hatschi Server 1.0")); + response.headers.insert(QStringLiteral("Content-Type"), QStringLiteral("text/html")); + response.headers.insert(QStringLiteral("Location"), QStringLiteral("/")); + + connection->sendResponse(response, "" % tr("Follow this link") % ""); +} + +void WifiLampApplication::handle404(HttpClientConnection *connection, const HttpRequest &request) +{ + HttpResponse response; + response.protocol = request.protocol; + response.statusCode = HttpResponse::StatusCode::NotFound; + response.headers.insert(QStringLiteral("Server"), QStringLiteral("Hatschi Server 1.0")); + response.headers.insert(QStringLiteral("Content-Type"), QStringLiteral("text/html")); + + connection->sendResponse(response, tr("Not Found")); +} + +QString WifiLampApplication::clientId(const WifiLampClient *client, bool forceIp) +{ + if(!client->name().isEmpty() && !forceIp) + return client->name(); + return client->peerAddress().toString() % ':' % QString::number(client->peerPort()); } diff --git a/wifilampapplication.h b/wifilampapplication.h index b3c297a..526f4ba 100644 --- a/wifilampapplication.h +++ b/wifilampapplication.h @@ -2,16 +2,43 @@ #include "webapplication.h" +#include +#include + class QJsonObject; +class QTcpServer; + +class WebServer; +class WifiLampClient; class WifiLampApplication : public WebApplication { Q_OBJECT public: - WifiLampApplication(const QJsonObject &config, QObject *parent = Q_NULLPTR); + WifiLampApplication(const QJsonObject &config, WebServer &webServer); void start() Q_DECL_OVERRIDE; void handleRequest(HttpClientConnection *connection, const HttpRequest &request) Q_DECL_OVERRIDE; + + const QSet &clients() const; + +private Q_SLOTS: + void acceptError(QAbstractSocket::SocketError socketError); + void newConnection(); + +private: + void handleRoot(HttpClientConnection *connection, const HttpRequest &request); + void redirectRoot(HttpClientConnection *connection, const HttpRequest &request); + void handle404(HttpClientConnection *connection, const HttpRequest &request); + static QString clientId(const WifiLampClient *client, bool forceIp = false); + + WebServer &m_webServer; + + QHostAddress m_hostAddress; + quint16 m_port; + QTcpServer *m_tcpServer; + + QSet m_clients; }; diff --git a/wifilampclient.cpp b/wifilampclient.cpp new file mode 100644 index 0000000..1732b11 --- /dev/null +++ b/wifilampclient.cpp @@ -0,0 +1,104 @@ +#include "wifilampclient.h" + +#include + +#include "wifilampapplication.h" + +WifiLampClient::WifiLampClient(QTcpSocket &socket, WifiLampApplication &application) : + QObject(&application), m_socket(socket), m_application(application), m_waitingForName(true) +{ + m_socket.setParent(this); + + connect(&m_socket, &QIODevice::readyRead, this, &WifiLampClient::readyRead); + connect(&m_socket, &QAbstractSocket::disconnected, this, &QObject::deleteLater); +} + +quint16 WifiLampClient::localPort() const +{ + return m_socket.localPort(); +} + +QHostAddress WifiLampClient::localAddress() const +{ + return m_socket.localAddress(); +} + +quint16 WifiLampClient::peerPort() const +{ + return m_socket.peerPort(); +} + +QHostAddress WifiLampClient::peerAddress() const +{ + return m_socket.peerAddress(); +} + +QString WifiLampClient::peerName() const +{ + return m_socket.peerName(); +} + +const QString &WifiLampClient::name() const +{ + return m_name; +} + +const QString &WifiLampClient::status() const +{ + return m_status; +} + +void WifiLampClient::on() +{ + m_socket.write(QByteArrayLiteral("1")); + m_status = QString(); +} + +void WifiLampClient::off() +{ + m_socket.write(QByteArrayLiteral("0")); + m_status = QString(); +} + +void WifiLampClient::toggle() +{ + m_socket.write(QByteArrayLiteral("t")); + m_status = QString(); +} + +void WifiLampClient::reboot() +{ + m_socket.write(QByteArrayLiteral("r")); + m_status = QString(); +} + +void WifiLampClient::requestStatus() +{ + m_socket.write(QByteArrayLiteral("s")); + m_status = QString(); +} + +void WifiLampClient::readyRead() +{ + m_buffer.append(m_socket.readAll()); + + int index; + while((index = m_buffer.indexOf(QByteArrayLiteral("\r\n"))) != -1) + { + QString line(m_buffer.left(index)); + m_buffer.remove(0, index + 2); + + if(m_waitingForName) + { + const auto iter = std::find_if(m_application.clients().constBegin(), m_application.clients().constEnd(), + [&line](auto client) { return client->name() == line; }); + if(iter != m_application.clients().constEnd()) + delete *iter; + + m_name = line; + m_waitingForName = false; + } + else + m_status = line; + } +} diff --git a/wifilampclient.h b/wifilampclient.h new file mode 100644 index 0000000..bde7572 --- /dev/null +++ b/wifilampclient.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +class QTcpSocket; +class WifiLampApplication; + +class WifiLampClient : public QObject +{ + Q_OBJECT +public: + explicit WifiLampClient(QTcpSocket &socket, WifiLampApplication &application); + + quint16 localPort() const; + QHostAddress localAddress() const; + quint16 peerPort() const; + QHostAddress peerAddress() const; + QString peerName() const; + + const QString &name() const; + const QString &status() const; + +public Q_SLOTS: + void on(); + void off(); + void toggle(); + void reboot(); + void requestStatus(); + +private Q_SLOTS: + void readyRead(); + +private: + QTcpSocket &m_socket; + WifiLampApplication &m_application; + QByteArray m_buffer; + bool m_waitingForName; + + QString m_name; + QString m_status; +}; diff --git a/wifilampplugin.cpp b/wifilampplugin.cpp index b9c0d3c..1096112 100644 --- a/wifilampplugin.cpp +++ b/wifilampplugin.cpp @@ -13,7 +13,7 @@ QString WifiLampPlugin::pluginName() const return QStringLiteral("wifilamp"); } -WebApplication *WifiLampPlugin::createApplication(const QJsonObject &config) const +WebApplication *WifiLampPlugin::createApplication(const QJsonObject &config, WebServer &webServer) const { - return new WifiLampApplication(config); + return new WifiLampApplication(config, webServer); } diff --git a/wifilampplugin.h b/wifilampplugin.h index 2599a64..70c3a73 100644 --- a/wifilampplugin.h +++ b/wifilampplugin.h @@ -12,5 +12,5 @@ public: WifiLampPlugin(QObject *parent = Q_NULLPTR); QString pluginName() const Q_DECL_OVERRIDE; - WebApplication *createApplication(const QJsonObject &config) const Q_DECL_OVERRIDE; + WebApplication *createApplication(const QJsonObject &config, WebServer &webServer) const Q_DECL_OVERRIDE; }; diff --git a/wifilampplugin.pro b/wifilampplugin.pro index dbff0c8..b142c7b 100644 --- a/wifilampplugin.pro +++ b/wifilampplugin.pro @@ -1,12 +1,14 @@ QT += core network -DBLIBS += webserverlib +DBLIBS += dbnetwork webserverlib HEADERS += wifilampplugin.h \ - wifilampapplication.h + wifilampapplication.h \ + wifilampclient.h SOURCES += wifilampplugin.cpp \ - wifilampapplication.cpp + wifilampapplication.cpp \ + wifilampclient.cpp FORMS +=