diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/WifiLamp.pro b/WifiLamp.pro new file mode 100644 index 0000000..db1e91c --- /dev/null +++ b/WifiLamp.pro @@ -0,0 +1,17 @@ +QT = core network + +CONFIG += c++11 +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS QT_DISABLE_DEPRECATED_BEFORE=0x060000 + +HEADERS += relaisclient.h \ + relaisserver.h \ + relaiswebserver.h + +SOURCES += main.cpp \ + relaisclient.cpp \ + relaisserver.cpp \ + relaiswebserver.cpp + +include(QtWebserver/QtWebserver.pri) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..24f763f --- /dev/null +++ b/main.cpp @@ -0,0 +1,26 @@ +#include +#include + +#include "relaisserver.h" +#include "relaiswebserver.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + RelaisServer server(&app); + if(!server.listen(QHostAddress::Any, 1234)) + { + qCritical() << "server could not start listening:" << server.errorString(); + return -1; + } + + RelaisWebserver webServer(&server, &app); + if(!webServer.listen(QHostAddress::Any, 8080)) + { + qCritical() << "webserver could not start listening:" << webServer.errorString(); + return -2; + } + + return app.exec(); +} diff --git a/relaisclient.cpp b/relaisclient.cpp new file mode 100644 index 0000000..5f9dea4 --- /dev/null +++ b/relaisclient.cpp @@ -0,0 +1,93 @@ +#include "relaisclient.h" + +#include + +#include "relaisserver.h" + +RelaisClient::RelaisClient(QTcpSocket *socket, RelaisServer *server) : + QObject(server), + m_socket(socket), + m_server(server), + m_state(Name) +{ + m_socket->setParent(this); + connect(m_socket, &QIODevice::readyRead, this, &RelaisClient::readyRead); + connect(m_socket, &QAbstractSocket::disconnected, this, &QObject::deleteLater); +} + +quint16 RelaisClient::localPort() const +{ + return m_socket->localPort(); +} + +QHostAddress RelaisClient::localAddress() const +{ + return m_socket->localAddress(); +} + +quint16 RelaisClient::peerPort() const +{ + return m_socket->peerPort(); +} + +QHostAddress RelaisClient::peerAddress() const +{ + return m_socket->peerAddress(); +} + +QString RelaisClient::peerName() const +{ + return m_socket->peerName(); +} + +const QString &RelaisClient::name() const +{ + return m_name; +} + +const QString &RelaisClient::status() const +{ + return m_status; +} + +void RelaisClient::on() +{ + m_socket->write(QByteArrayLiteral("1")); + m_status = QString(); +} + +void RelaisClient::off() +{ + m_socket->write(QByteArrayLiteral("0")); + m_status = QString(); +} + +void RelaisClient::toggle() +{ + m_socket->write(QByteArrayLiteral("t")); + m_status = QString(); +} + +void RelaisClient::requestStatus() +{ + m_socket->write(QByteArrayLiteral("s")); + m_status = QString(); +} + +void RelaisClient::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); + + switch(m_state) + { + case Name: m_name = line; m_state = Status; break; + case Status: m_status = line; break; + } + } +} diff --git a/relaisclient.h b/relaisclient.h new file mode 100644 index 0000000..fef9544 --- /dev/null +++ b/relaisclient.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +class QTcpSocket; + +class RelaisServer; + +class RelaisClient : public QObject +{ + Q_OBJECT + +public: + explicit RelaisClient(QTcpSocket *socket, RelaisServer *server); + + 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 requestStatus(); + +private Q_SLOTS: + void readyRead(); + +private: + QTcpSocket *m_socket; + RelaisServer *m_server; + QByteArray m_buffer; + enum { Name, Status } m_state; + + QString m_name; + QString m_status; +}; diff --git a/relaisserver.cpp b/relaisserver.cpp new file mode 100644 index 0000000..effebfc --- /dev/null +++ b/relaisserver.cpp @@ -0,0 +1,113 @@ +#include "relaisserver.h" + +#include + +#include "relaisclient.h" + +RelaisServer::RelaisServer(QObject *parent) : + QObject(parent), + m_server(new QTcpServer(this)) +{ + connect(m_server, &QTcpServer::acceptError, this, &RelaisServer::acceptError); + connect(m_server, &QTcpServer::newConnection, this, &RelaisServer::newConnection); +} + +bool RelaisServer::listen(const QHostAddress &address, quint16 port) +{ + return m_server->listen(address, port); +} + +void RelaisServer::close() +{ + m_server->close(); +} + +bool RelaisServer::isListening() const +{ + return m_server->isListening(); +} + +void RelaisServer::setMaxPendingConnections(int numConnections) +{ + m_server->setMaxPendingConnections(numConnections); +} + +int RelaisServer::maxPendingConnections() const +{ + return m_server->maxPendingConnections(); +} + +quint16 RelaisServer::serverPort() const +{ + return m_server->serverPort(); +} + +QHostAddress RelaisServer::serverAddress() const +{ + return m_server->serverAddress(); +} + +qintptr RelaisServer::socketDescriptor() const +{ + return m_server->socketDescriptor(); +} + +bool RelaisServer::setSocketDescriptor(qintptr socketDescriptor) +{ + return m_server->setSocketDescriptor(socketDescriptor); +} + +bool RelaisServer::hasPendingConnections() const +{ + return m_server->hasPendingConnections(); +} + +QAbstractSocket::SocketError RelaisServer::serverError() const +{ + return m_server->serverError(); +} + +QString RelaisServer::errorString() const +{ + return m_server->errorString(); +} + +void RelaisServer::pauseAccepting() +{ + m_server->pauseAccepting(); +} + +void RelaisServer::resumeAccepting() +{ + m_server->resumeAccepting(); +} + +#ifndef QT_NO_NETWORKPROXY +void RelaisServer::setProxy(const QNetworkProxy &networkProxy) +{ + m_server->setProxy(networkProxy); +} + +QNetworkProxy RelaisServer::proxy() const +{ + return m_server->proxy(); +} +#endif + +const QSet &RelaisServer::clients() const +{ + return m_clients; +} + +void RelaisServer::newConnection() +{ + auto connection = m_server->nextPendingConnection(); + if(!connection) + return; + + auto client = new RelaisClient(connection, this); + m_clients.insert(client); + connect(client, &QObject::destroyed, this, [=](){ + m_clients.remove(client); + }); +} diff --git a/relaisserver.h b/relaisserver.h new file mode 100644 index 0000000..45cd2a3 --- /dev/null +++ b/relaisserver.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#ifndef QT_NO_NETWORKPROXY +#include +#endif + +class QTcpServer; + +class RelaisClient; + +class RelaisServer : public QObject +{ + Q_OBJECT + +public: + explicit RelaisServer(QObject *parent = Q_NULLPTR); + + bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0); + void close(); + + bool isListening() const; + + void setMaxPendingConnections(int numConnections); + int maxPendingConnections() const; + + quint16 serverPort() const; + QHostAddress serverAddress() const; + + qintptr socketDescriptor() const; + bool setSocketDescriptor(qintptr socketDescriptor); + + bool hasPendingConnections() const; + + QAbstractSocket::SocketError serverError() const; + QString errorString() const; + + void pauseAccepting(); + void resumeAccepting(); + +#ifndef QT_NO_NETWORKPROXY + void setProxy(const QNetworkProxy &networkProxy); + QNetworkProxy proxy() const; +#endif + + const QSet &clients() const; + +Q_SIGNALS: + void acceptError(QAbstractSocket::SocketError socketError); + +protected: + QSet m_clients; + +private Q_SLOTS: + void newConnection(); + +private: + QTcpServer *m_server; +}; diff --git a/relaiswebserver.cpp b/relaiswebserver.cpp new file mode 100644 index 0000000..9cab43c --- /dev/null +++ b/relaiswebserver.cpp @@ -0,0 +1,161 @@ +#include "relaiswebserver.h" + +#include + +#include "httpclientconnection.h" +#include "httpcontainers.h" +#include "relaisserver.h" +#include "relaisclient.h" + +RelaisWebserver::RelaisWebserver(RelaisServer *relaisServer, QObject *parent) : + HttpServer(parent), + m_relaisServer(relaisServer) +{ +} + +void RelaisWebserver::handleRequest(HttpClientConnection *connection, const HttpRequest &request) +{ + qDebug() << request.path; + + if(!request.path.startsWith('/')) + { + HttpResponse response; + response.protocol = request.protocol; + response.statusCode = HttpResponse::StatusCode::BadRequest; + response.body = "Path does not start with /"; + response.headers.insert(QStringLiteral("Server"), QStringLiteral("Hatschi Server 1.0")); + response.headers.insert(QStringLiteral("Content-Type"), QStringLiteral("text/html")); + + connection->sendResponse(response); + return; + } + + if(request.path == QStringLiteral("/")) + { + handleRoot(connection, request); + } + else if(request.path.startsWith("/devices/")) + { + auto parts = request.path.split('/'); + if(parts.count() != 4) + { + handle404(connection, request); + return; + } + + RelaisClient *client = Q_NULLPTR; + + for(auto _client : m_relaisServer->clients()) + { + if(!_client->name().isEmpty() && + _client->name() == parts.at(2)) + { + client = _client; + break; + } + } + + if(!client) + { + handle404(connection, request); + return; + } + + if(parts.at(3) == "on") + { + client->on(); + redirectRoot(connection, request); + return; + } + else if(parts.at(3) == "off") + { + client->off(); + redirectRoot(connection, request); + return; + } + else if(parts.at(3) == "toggle") + { + client->toggle(); + redirectRoot(connection, request); + return; + } + else + { + handle404(connection, request); + return; + } + } + else + { + handle404(connection, request); + } +} + +void RelaisWebserver::handleRoot(HttpClientConnection *connection, const HttpRequest &request) +{ + QString output = "

" % tr("%0 clients").arg(m_relaisServer->clients().count()) % "

"; + + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + output.append(""); + for(auto client : m_relaisServer->clients()) + { + output.append(""); + output.append(""); + output.append(""); + output.append(""); + if(!client->name().isEmpty()) + { + output.append(""); + } + output.append(""); + } + output.append(""); + output.append("
IP-AdressNameStatusActions
" % client->peerAddress().toString() % ':' % QString::number(client->peerPort()) % "" % client->name() % "" % client->status() % "name().toHtmlEscaped() % "/toggle\">" % tr("toggle") % " "); + if(client->status() != QStringLiteral("on")) + output.append("name().toHtmlEscaped() % "/on\">" % tr("on") % " "); + if(client->status() != QStringLiteral("off")) + output.append("name().toHtmlEscaped() % "/off\">" % tr("off") % " "); + output.append("
"); + + HttpResponse response; + response.protocol = request.protocol; + response.statusCode = HttpResponse::StatusCode::OK; + response.body = output.toUtf8(); + response.headers.insert(QStringLiteral("Server"), QStringLiteral("Hatschi Server 1.0")); + response.headers.insert(QStringLiteral("Content-Type"), QStringLiteral("text/html")); + + connection->sendResponse(response); +} + +void RelaisWebserver::redirectRoot(HttpClientConnection *connection, const HttpRequest &request) +{ + HttpResponse response; + response.protocol = request.protocol; + response.statusCode = HttpResponse::StatusCode::Found; + response.body = QByteArrayLiteral("Follow this link"); + 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); +} + +void RelaisWebserver::handle404(HttpClientConnection *connection, const HttpRequest &request) +{ + HttpResponse response; + response.protocol = request.protocol; + response.statusCode = HttpResponse::StatusCode::NotFound; + response.body = "Not Found"; + response.headers.insert(QStringLiteral("Server"), QStringLiteral("Hatschi Server 1.0")); + response.headers.insert(QStringLiteral("Content-Type"), QStringLiteral("text/html")); + + connection->sendResponse(response); +} diff --git a/relaiswebserver.h b/relaiswebserver.h new file mode 100644 index 0000000..ca1d7da --- /dev/null +++ b/relaiswebserver.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "httpserver.h" + +class RelaisServer; +class HttpClientConnection; +class HttpRequest; + +class RelaisWebserver : public HttpServer +{ + Q_OBJECT + +public: + explicit RelaisWebserver(RelaisServer *relaisServer, QObject *parent = Q_NULLPTR); + +protected: + void handleRequest(HttpClientConnection *connection, const HttpRequest &request) Q_DECL_OVERRIDE; + +private: + void handleRoot(HttpClientConnection *connection, const HttpRequest &request); + void redirectRoot(HttpClientConnection *connection, const HttpRequest &request); + void handle404(HttpClientConnection *connection, const HttpRequest &request); + + RelaisServer *m_relaisServer; +};