Import existing sources

This commit is contained in:
2022-09-25 01:31:36 +02:00
parent 38704e2f03
commit 68a17e2442
26 changed files with 1660 additions and 0 deletions
+331
View File
@@ -0,0 +1,331 @@
#include "espremoteagent.h"
#include <QDebug>
#include <QUrl>
#include <QUrlQuery>
#include "webservercontainer.h"
#include "webserverclientconnection.h"
#include "espremoteagentcontainers.h"
#include "espremoteport.h"
EspRemoteAgent::EspRemoteAgent(std::vector<SerialPortConfig> &&serialPortConfigs, QObject *parent) :
AbstractWebserver{parent}
{
m_ports.reserve(serialPortConfigs.size());
for (auto &config : serialPortConfigs)
m_ports.emplace_back(std::make_unique<EspRemotePort>(std::move(config), this));
}
EspRemoteAgent::~EspRemoteAgent() = default;
void EspRemoteAgent::requestReceived(WebserverClientConnection &client, const Request &request)
{
const QUrl url{request.path};
const QUrlQuery query{url};
if (url.path() == "/")
{
sendRootResponse(client, url, query);
}
else if (url.path() == "/open")
{
sendOpenResponse(client, url, query);
}
else if (url.path() == "/close")
{
sendCloseResponse(client, url, query);
}
else if (url.path() == "/reboot")
{
sendRebootResponse(client, url, query);
}
else if (url.path() == "/setDTR")
{
sendSetDTRResponse(client, url, query);
}
else if (url.path() == "/setRTS")
{
sendSetRTSResponse(client, url, query);
}
else
if (!client.sendFullResponse(404, "Not Found", {{"Content-Type", "text/plain"}}, "The requested path \"" + request.path + "\" was not found."))
qWarning() << "sending response failed";
}
void EspRemoteAgent::sendRootResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query)
{
QString content =
"<html>"
"<head>"
"<title>ESP Remote Agent</title>"
"</head>"
"<body>"
"<table border=\"1\">"
"<thead>"
"<tr>"
"<th>ID</th>"
"<th>Port</th>"
"<th>Status</th>"
"<th>Message</th>"
"<th>Actions</th>"
"<th>Log output</th>"
"</tr>"
"</thead>"
"<tbody>";
std::size_t i{};
for (const auto &port : m_ports)
{
const auto currentId = i++;
content += QStringLiteral("<tr>"
"<td>%0</td>"
"<td>%1</td>"
"<td>%2</td>"
"<td>%3</td>"
"<td>"
"<a href=\"open?id=%0\">Open</a> "
"<a href=\"close?id=%0\">Close</a><br />"
"<a href=\"reboot?id=%0\">Reboot</a><br />"
"DTR %4 <a href=\"setDTR?id=%0&set=%5\">Toggle</a><br />"
"RTS %6 <a href=\"setRTS?id=%0&set=%7\">Toggle</a>"
"</td>"
"<td><pre>%8</pre></td>"
"</tr>")
.arg(currentId)
.arg(port->port())
.arg(port->status())
.arg(port->message())
.arg(port->isDataTerminalReady() ? "On" : "Off")
.arg(port->isDataTerminalReady() ? "false" : "true")
.arg(port->isRequestToSend() ? "On" : "Off")
.arg(port->isRequestToSend() ? "false" : "true")
.arg(port->logOutput());
}
content += "</tbody>"
"</table>"
"</body>"
"</html>";
if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/html"}}, content.toUtf8()))
qWarning() << "sending response failed";
}
void EspRemoteAgent::sendOpenResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query)
{
if (!query.hasQueryItem("id"))
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id missing"))
qWarning() << "sending response failed";
return;
}
const auto idStr = query.queryItemValue("id");
bool ok{};
const auto id = idStr.toInt(&ok);
if (!ok)
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse id"))
qWarning() << "sending response failed";
return;
}
if (id < 0 || id >= m_ports.size())
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id out of range"))
qWarning() << "sending response failed";
return;
}
if ((*std::next(std::begin(m_ports), id))->tryOpen())
{
if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/plain"}}, "Port opened successfully!"))
qWarning() << "sending response failed";
return;
}
else
{
if (!client.sendFullResponse(500, "Internal Server Error", {{"Content-Type", "text/plain"}}, "Opening port failed!"))
qWarning() << "sending response failed";
return;
}
}
void EspRemoteAgent::sendCloseResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query)
{
if (!query.hasQueryItem("id"))
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id missing"))
qWarning() << "sending response failed";
return;
}
const auto idStr = query.queryItemValue("id");
bool ok{};
const auto id = idStr.toInt(&ok);
if (!ok)
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse id"))
qWarning() << "sending response failed";
return;
}
if (id < 0 || id >= m_ports.size())
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id out of range"))
qWarning() << "sending response failed";
return;
}
(*std::next(std::begin(m_ports), id))->close();
if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/plain"}}, "Port closed successfully!"))
qWarning() << "sending response failed";
}
void EspRemoteAgent::sendRebootResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query)
{
if (!query.hasQueryItem("id"))
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id missing"))
qWarning() << "sending response failed";
return;
}
const auto idStr = query.queryItemValue("id");
bool ok{};
const auto id = idStr.toInt(&ok);
if (!ok)
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse id"))
qWarning() << "sending response failed";
return;
}
if (id < 0 || id >= m_ports.size())
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id out of range"))
qWarning() << "sending response failed";
return;
}
if ((*std::next(std::begin(m_ports), id))->reboot())
{
if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/plain"}}, "Reboot successfully!"))
qWarning() << "sending response failed";
return;
}
else
{
if (!client.sendFullResponse(500, "Internal Server Error", {{"Content-Type", "text/plain"}}, "Reboot failed!"))
qWarning() << "sending response failed";
return;
}
}
void EspRemoteAgent::sendSetDTRResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query)
{
if (!query.hasQueryItem("id"))
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id missing"))
qWarning() << "sending response failed";
return;
}
const auto idStr = query.queryItemValue("id");
bool ok{};
const auto id = idStr.toInt(&ok);
if (!ok)
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse id"))
qWarning() << "sending response failed";
return;
}
if (id < 0 || id >= m_ports.size())
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id out of range"))
qWarning() << "sending response failed";
return;
}
if (!query.hasQueryItem("set"))
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "set missing"))
qWarning() << "sending response failed";
return;
}
bool set;
if (const auto setStr = query.queryItemValue("set"); setStr == "true")
set = true;
else if (setStr == "false")
set = false;
else
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse set"))
qWarning() << "sending response failed";
return;
}
if ((*std::next(std::begin(m_ports), id))->setDataTerminalReady(set))
{
if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/plain"}}, "Port set DTR successfully!"))
qWarning() << "sending response failed";
return;
}
else
{
if (!client.sendFullResponse(500, "Internal Server Error", {{"Content-Type", "text/plain"}}, "Set port DTR failed!"))
qWarning() << "sending response failed";
return;
}
}
void EspRemoteAgent::sendSetRTSResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query)
{
if (!query.hasQueryItem("id"))
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id missing"))
qWarning() << "sending response failed";
return;
}
const auto idStr = query.queryItemValue("id");
bool ok{};
const auto id = idStr.toInt(&ok);
if (!ok)
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse id"))
qWarning() << "sending response failed";
return;
}
if (id < 0 || id >= m_ports.size())
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "id out of range"))
qWarning() << "sending response failed";
return;
}
if (!query.hasQueryItem("set"))
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "set missing"))
qWarning() << "sending response failed";
return;
}
bool set;
if (const auto setStr = query.queryItemValue("set"); setStr == "true")
set = true;
else if (setStr == "false")
set = false;
else
{
if (!client.sendFullResponse(400, "Bad Request", {{"Content-Type", "text/plain"}}, "could not parse set"))
qWarning() << "sending response failed";
return;
}
if ((*std::next(std::begin(m_ports), id))->setRequestToSend(set))
{
if (!client.sendFullResponse(200, "Ok", {{"Content-Type", "text/plain"}}, "Port set RTS successfully!"))
qWarning() << "sending response failed";
return;
}
else
{
if (!client.sendFullResponse(500, "Internal Server Error", {{"Content-Type", "text/plain"}}, "Set port RTS failed!"))
qWarning() << "sending response failed";
return;
}
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <vector>
#include <memory>
#include "abstractwebserver.h"
class SerialPortConfig;
class WebserverClientConnection;
class Request;
class EspRemotePort;
class QUrl;
class QUrlQuery;
class EspRemoteAgent : public AbstractWebserver
{
Q_OBJECT
public:
explicit EspRemoteAgent(std::vector<SerialPortConfig> &&serialPortConfigs, QObject *parent = nullptr);
~EspRemoteAgent() override;
protected:
void requestReceived(WebserverClientConnection &client, const Request &request) override;
private:
void sendRootResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query);
void sendOpenResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query);
void sendCloseResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query);
void sendRebootResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query);
void sendSetDTRResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query);
void sendSetRTSResponse(WebserverClientConnection &client, const QUrl &url, const QUrlQuery &query);
std::vector<std::unique_ptr<EspRemotePort>> m_ports;
};
+13
View File
@@ -0,0 +1,13 @@
[Webserver]
listen=Any
port=80
[PortA]
port=/dev/ttyUSB0
baudrate=115200
url=ws://office-pi:1235/charger0
[PortB]
port=/dev/ttyUSB1
baudrate=115200
url=ws://office-pi:1235/charger1
+31
View File
@@ -0,0 +1,31 @@
QT = core network serialport websockets
TARGET = espremoteagent
TEMPLATE = app
CONFIG += console
CONFIG -= app_bundle
PROJECT_ROOT = ..
DESTDIR = $${OUT_PWD}/$${PROJECT_ROOT}/bin
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
DBLIBS += webserver
HEADERS += \
espremoteagent.h \
espremoteagentcontainers.h \
espremoteport.h
SOURCES += \
espremoteport.cpp \
main.cpp \
espremoteagent.cpp
OTHER_FILES += \
espremoteagent.ini
include($${PROJECT_ROOT}/project.pri)
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <QString>
#include <QUrl>
struct SerialPortConfig
{
QString port;
int baudrate;
QUrl url;
};
+225
View File
@@ -0,0 +1,225 @@
#include "espremoteport.h"
#include <QSerialPort>
#include <QWebSocket>
#include <QTimerEvent>
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTimer>
EspRemotePort::EspRemotePort(SerialPortConfig &&config, QObject *parent) :
QObject{parent},
m_config{std::move(config)},
m_port{std::make_unique<QSerialPort>(m_config.port)},
m_websocket{m_config.url.isEmpty() ? nullptr : std::make_unique<QWebSocket>(QString{}, QWebSocketProtocol::VersionLatest, this)}
{
connect(m_port.get(), &QSerialPort::readyRead, this, &EspRemotePort::serialReadyRead);
if (m_websocket)
{
connect(m_websocket.get(), &QWebSocket::connected, this, &EspRemotePort::websocketConnected);
connect(m_websocket.get(), &QWebSocket::disconnected, this, &EspRemotePort::websocketDisconnected);
connect(m_websocket.get(), qOverload<QAbstractSocket::SocketError>(&QWebSocket::error), this, &EspRemotePort::websocketError);
connect(m_websocket.get(), &QWebSocket::textMessageReceived, this, &EspRemotePort::websocketTextMessageReceived);
qDebug() << "connecting to" << m_config.url;
m_websocket->open(m_config.url);
}
tryOpen();
}
EspRemotePort::~EspRemotePort() = default;
QString EspRemotePort::status() const
{
if (m_port->isOpen())
return tr("Open");
else
return tr("Not open");
}
QString EspRemotePort::logOutput() const
{
QString str;
for (const auto &line : m_logOutput)
{
if (!str.isEmpty())
str += "\n";
str += line.toHtmlEscaped();
}
return str;
}
bool EspRemotePort::reboot()
{
bool set = m_port->isDataTerminalReady();
Q_ASSERT(m_port);
if (!m_port->setDataTerminalReady(!set))
return false;
QTimer::singleShot(100, m_port.get(), [set,port=m_port.get()](){
if (!port->setDataTerminalReady(set))
qWarning() << "reboot failed";
});
return true;
}
bool EspRemotePort::setDataTerminalReady(bool set)
{
return m_port->setDataTerminalReady(set);
}
bool EspRemotePort::isDataTerminalReady()
{
return m_port->isDataTerminalReady();
}
bool EspRemotePort::setRequestToSend(bool set)
{
return m_port->setRequestToSend(set);
}
bool EspRemotePort::isRequestToSend()
{
return m_port->isRequestToSend();
}
bool EspRemotePort::tryOpen()
{
m_port->close();
if (!m_port->setBaudRate(m_config.baudrate))
qWarning() << "could not set baud rate" << m_config.baudrate;
if (!m_port->open(QIODevice::ReadWrite))
{
m_message = tr("Could not open port because %0").arg(m_port->errorString());
qWarning() << m_message;
return false;
}
return true;
}
void EspRemotePort::close()
{
m_port->close();
}
void EspRemotePort::timerEvent(QTimerEvent *event)
{
if (event->timerId() == m_reconnectTimerId)
{
m_reconnectTimerId = -1;
if (!m_config.url.isEmpty())
{
Q_ASSERT(m_websocket);
qDebug() << "reconnecting to" << m_config.url;
m_websocket->open(m_config.url);
}
}
else
QObject::timerEvent(event);
}
void EspRemotePort::serialReadyRead()
{
while (m_port->canReadLine())
{
auto line = m_port->readLine();
if (line.endsWith('\n'))
{
line.chop(1);
if (line.endsWith('\r'))
line.chop(1);
}
// qDebug() << line;
if (m_websocket)
{
m_websocket->sendTextMessage(QJsonDocument{QJsonObject{
{"type", "log"},
{"line", QString{line}},
}}.toJson());
}
m_logOutput.push(std::move(line));
while (m_logOutput.size() > 10)
m_logOutput.pop();
}
}
void EspRemotePort::websocketConnected()
{
qDebug() << "called";
if (m_reconnectTimerId != -1)
killTimer(m_reconnectTimerId);
}
void EspRemotePort::websocketDisconnected()
{
qDebug() << "called";
}
void EspRemotePort::websocketError(QAbstractSocket::SocketError error)
{
qDebug() << "called" << error;
if (m_reconnectTimerId != -1)
killTimer(m_reconnectTimerId);
m_reconnectTimerId = startTimer(5000);
}
void EspRemotePort::websocketTextMessageReceived(const QString &message)
{
// qDebug() << message;
QJsonParseError error;
const auto doc = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error != QJsonParseError::NoError)
{
qWarning() << "could not parse json command:" << error.errorString();
return;
}
if (!doc.isObject())
{
qWarning() << "json command is not an object";
return;
}
const auto obj = doc.object();
if (!obj.contains("type"))
{
qWarning() << "json command does not contain a type";
return;
}
const auto typeVal = obj.value("type");
if (!typeVal.isString())
{
qWarning() << "json command type is not a string";
return;
}
const auto type = typeVal.toString();
if (type == "reboot")
{
reboot();
}
else
qWarning() << "unknown command type" << type;
}
+56
View File
@@ -0,0 +1,56 @@
#pragma once
#include <memory>
#include <QObject>
#include <QAbstractSocket>
#include "espremoteagentcontainers.h"
#include "webserverutils.h"
class QSerialPort;
class QWebSocket;
class EspRemotePort : public QObject
{
Q_OBJECT
public:
explicit EspRemotePort(SerialPortConfig &&config, QObject *parent = nullptr);
~EspRemotePort() override;
QString port() const { return m_config.port; }
QString status() const;
QString message() const { return m_message; }
QString logOutput() const;
bool reboot();
bool setDataTerminalReady(bool set);
bool isDataTerminalReady();
bool setRequestToSend(bool set);
bool isRequestToSend();
bool tryOpen();
void close();
protected:
void timerEvent(QTimerEvent *event) override;
private slots:
void serialReadyRead();
void websocketConnected();
void websocketDisconnected();
void websocketError(QAbstractSocket::SocketError error);
void websocketTextMessageReceived(const QString &message);
private:
SerialPortConfig m_config;
const std::unique_ptr<QSerialPort> m_port;
const std::unique_ptr<QWebSocket> m_websocket;
QString m_message;
iterable_queue<QString> m_logOutput;
int m_reconnectTimerId{-1};
};
+67
View File
@@ -0,0 +1,67 @@
#include <QCoreApplication>
#include <QSettings>
#include <QUrl>
#include "espremoteagent.h"
#include "espremoteagentcontainers.h"
#include "webserverutils.h"
int main(int argc, char *argv[])
{
qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} "
"["
"%{if-debug}D%{endif}"
"%{if-info}I%{endif}"
"%{if-warning}W%{endif}"
"%{if-critical}C%{endif}"
"%{if-fatal}F%{endif}"
"] "
"%{function}(): "
"%{message}"));
QCoreApplication app{argc, argv};
QSettings settings{"espremoteagent.ini", QSettings::IniFormat};
std::vector<SerialPortConfig> serialPortConfigs;
const auto probePort = [&](auto group){
auto port = settings.value(QStringLiteral("%0/port").arg(group)).toString();
if (port.isEmpty())
return;
QUrl url;
if (auto urlStr = settings.value(QStringLiteral("%0/url").arg(group)).toString(); !urlStr.isEmpty())
url = QUrl{std::move(urlStr)};
int baudrate{};
bool ok{};
baudrate = settings.value(QStringLiteral("%0/baudrate").arg(group)).toInt(&ok);
if (!ok)
qFatal("could not parse baudrate for %s", qPrintable(group));
serialPortConfigs.emplace_back(SerialPortConfig{
.port=std::move(port),
.baudrate=baudrate,
.url=std::move(url)
});
};
probePort("PortA");
probePort("PortB");
QHostAddress webserverListen = parseHostAddress(settings.value("Webserver/listen").toString());
int webserverPort;
{
bool ok{};
webserverPort = settings.value("Webserver/port", 1234).toInt(&ok);
if (!ok)
qFatal("could not parse webserver port");
}
EspRemoteAgent agent{std::move(serialPortConfigs)};
if (!agent.listen(webserverListen, webserverPort))
qFatal("could not start listening %s", qPrintable(agent.errorString()));
return app.exec();
}