Implemented WifiLamp server for lamps

This commit is contained in:
0xFEEDC0DE64
2018-09-22 07:24:51 +02:00
parent 1d9bfba588
commit e9e18f227f
7 changed files with 388 additions and 11 deletions

View File

@@ -1,24 +1,226 @@
#include "wifilampapplication.h"
#include <QJsonObject>
#include <QJsonValue>
#include <QTcpServer>
#include <QStringBuilder>
#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<QString, std::function<void(WifiLampClient*)> > 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<WifiLampClient *> &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 = "<h1>Lampen-Steuerung</h1>";
output.append("<a href=\"/refresh\">Alle aktualisieren</a>");
output.append("<table border=\"1\">");
output.append("<thead>");
output.append("<tr>");
output.append("<th>IP-Adress</th>");
output.append("<th>Name</th>");
output.append("<th>Status</th>");
output.append("<th>Actions</th>");
output.append("</tr>");
output.append("</thead>");
output.append("<tbody>");
for(auto client : m_clients)
{
output.append("<tr>");
output.append("<td>" % clientId(client, true).toHtmlEscaped() % "</td>");
output.append("<td>" % client->name().toHtmlEscaped() % "</td>");
output.append("<td>" % client->status().toHtmlEscaped() % "</td>");
output.append("<td>");
output.append("<a href=\"/devices/" % clientId(client).toHtmlEscaped() % "/toggle\">" % tr("Toggle") % "</a> ");
output.append("<a href=\"/devices/" % clientId(client).toHtmlEscaped() % "/on\">" % tr("On") % "</a> ");
output.append("<a href=\"/devices/" % clientId(client).toHtmlEscaped() % "/off\">" % tr("Off") % "</a> ");
output.append("<a href=\"/devices/" % clientId(client).toHtmlEscaped() % "/refresh\">" % tr("Refresh") % "</a> ");
output.append("<a href=\"/devices/" % clientId(client).toHtmlEscaped() % "/reboot\">" % tr("Reboot") % "</a> ");
output.append("<a href=\"/devices/" % clientId(client).toHtmlEscaped() % "/delete\">" % tr("Delete") % "</a> ");
output.append("</td>");
output.append("</tr>");
}
output.append("</tbody>");
output.append("</table>");
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, "<a href=\"/\">" % tr("Follow this link") % "</a>");
}
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());
}

View File

@@ -2,16 +2,43 @@
#include "webapplication.h"
#include <QAbstractSocket>
#include <QHostAddress>
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<WifiLampClient*> &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<WifiLampClient*> m_clients;
};

104
wifilampclient.cpp Normal file
View File

@@ -0,0 +1,104 @@
#include "wifilampclient.h"
#include <QTcpSocket>
#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;
}
}

42
wifilampclient.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include <QObject>
#include <QHostAddress>
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;
};

View File

@@ -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);
}

View File

@@ -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;
};

View File

@@ -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 +=