Implemented WifiLamp server for lamps
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
@@ -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
104
wifilampclient.cpp
Normal 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
42
wifilampclient.h
Normal 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;
|
||||
};
|
@@ -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);
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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 +=
|
||||
|
||||
|
Reference in New Issue
Block a user