GrabBag/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Src/ParkingSpaceGuideRemoteServer.cpp

368 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "ParkingSpaceGuideRemoteServer.h"
#include <utility>
#include <QDateTime>
#include <QHostAddress>
#include <QHostInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QSysInfo>
#include <QUdpSocket>
#include "IVrZeroMQPubSub.h"
#include "IVrZeroMQServer.h"
#include "VrLog.h"
namespace {
const char kRemoteProtocol[] = "ParkingSpaceGuideRemote";
const char kRemoteProtocolVersion[] = "1.0";
const char kRemoteService[] = "ParkingSpaceGuide";
QJsonObject ErrorResponse(const QString& command,
int code,
const QString& message)
{
QJsonObject response;
response["ok"] = false;
response["code"] = code;
response["cmd"] = command;
response["message"] = message;
response["protocol"] = QString::fromLatin1(kRemoteProtocol);
response["version"] = QString::fromLatin1(kRemoteProtocolVersion);
return response;
}
} // namespace
ParkingSpaceGuideRemoteServer::ParkingSpaceGuideRemoteServer(QObject* parent)
: QObject(parent)
{
}
ParkingSpaceGuideRemoteServer::~ParkingSpaceGuideRemoteServer()
{
Stop();
}
bool ParkingSpaceGuideRemoteServer::Start(
const RemoteViewConfig& config,
ModelHandler modelHandler,
StartHandler startHandler,
StopHandler stopHandler,
StatusProvider statusProvider,
RunningProvider runningProvider,
LogHandler logHandler)
{
Stop();
m_config = config;
m_modelHandler = std::move(modelHandler);
m_startHandler = std::move(startHandler);
m_stopHandler = std::move(stopHandler);
m_statusProvider = std::move(statusProvider);
m_runningProvider = std::move(runningProvider);
m_logHandler = std::move(logHandler);
SetError(QString());
if (!m_config.enabled) {
return true;
}
if (!IVrZeroMQPublisher::CreateObject(&m_statusPublisher) ||
!m_statusPublisher ||
m_statusPublisher->Init(m_config.publishPort) != 0) {
SetError(QStringLiteral("初始化远程状态发布端口失败:%1")
.arg(m_config.publishPort));
Stop();
return false;
}
if (!IVrZeroMQServer::CreateObject(&m_controlServer) || !m_controlServer) {
SetError(QStringLiteral("创建远程控制服务失败"));
Stop();
return false;
}
const auto receiveHandler = [this](const char* data, size_t length) {
const QByteArray request(data, static_cast<int>(length));
const QByteArray response = HandleControlMessage(request);
return std::string(response.constData(),
static_cast<size_t>(response.size()));
};
if (m_controlServer->Init(m_config.controlPort,
FunMQEvent(),
receiveHandler,
false) != 0) {
SetError(QStringLiteral("初始化远程控制端口失败:%1")
.arg(m_config.controlPort));
Stop();
return false;
}
m_discoverySocket = new QUdpSocket(this);
if (!m_discoverySocket->bind(
QHostAddress::AnyIPv4,
static_cast<quint16>(m_config.discoveryPort),
QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) {
SetError(QStringLiteral("初始化UDP发现端口失败%1%2")
.arg(m_config.discoveryPort)
.arg(m_discoverySocket->errorString()));
Stop();
return false;
}
connect(m_discoverySocket, &QUdpSocket::readyRead, this, [this]() {
HandleDiscoveryDatagrams();
});
m_running.store(true);
LOG_INFO("ParkingSpaceGuide remote service discovery=%d control=%d publish=%d topic=%s\n",
m_config.discoveryPort,
m_config.controlPort,
m_config.publishPort,
m_config.topic.c_str());
return true;
}
void ParkingSpaceGuideRemoteServer::Stop()
{
m_running.store(false);
if (m_discoverySocket) {
m_discoverySocket->close();
delete m_discoverySocket;
m_discoverySocket = nullptr;
}
if (m_controlServer) {
delete m_controlServer;
m_controlServer = nullptr;
}
{
std::lock_guard<std::mutex> lock(m_publishMutex);
if (m_statusPublisher) {
m_statusPublisher->UnInit();
delete m_statusPublisher;
m_statusPublisher = nullptr;
}
}
}
bool ParkingSpaceGuideRemoteServer::IsRunning() const
{
return m_running.load();
}
bool ParkingSpaceGuideRemoteServer::PublishStatus(
const std::string& statusJson)
{
std::lock_guard<std::mutex> lock(m_publishMutex);
if (!m_running.load() || !m_statusPublisher || statusJson.empty()) {
return false;
}
const int result = m_statusPublisher->Publish(
m_config.topic.c_str(),
statusJson.data(),
statusJson.size());
if (result != 0) {
SetError(QStringLiteral("远程状态发布失败:%1").arg(result));
return false;
}
return true;
}
QString ParkingSpaceGuideRemoteServer::LastError() const
{
std::lock_guard<std::mutex> lock(m_errorMutex);
return m_lastError;
}
QJsonObject ParkingSpaceGuideRemoteServer::BuildServerInfo(
const QString& command) const
{
QJsonObject response;
response["ok"] = true;
response["code"] = 0;
response["cmd"] = command;
response["protocol"] = QString::fromLatin1(kRemoteProtocol);
response["version"] = QString::fromLatin1(kRemoteProtocolVersion);
response["service"] = QString::fromLatin1(kRemoteService);
response["deviceName"] = QStringLiteral("ParkingSpaceGuideApp");
response["deviceId"] = QString::fromStdString(DeviceId());
response["controlPort"] = m_config.controlPort;
response["publishPort"] = m_config.publishPort;
response["topic"] = QString::fromStdString(m_config.topic);
response["guidanceRunning"] = m_runningProvider
? m_runningProvider()
: false;
response["timestamp"] = QDateTime::currentMSecsSinceEpoch();
return response;
}
QByteArray ParkingSpaceGuideRemoteServer::HandleControlMessage(
const QByteArray& request)
{
QJsonParseError parseError;
const QJsonDocument document = QJsonDocument::fromJson(request, &parseError);
if (parseError.error != QJsonParseError::NoError || !document.isObject()) {
return QJsonDocument(ErrorResponse(QString(), -1,
QStringLiteral("控制请求不是有效JSON")))
.toJson(QJsonDocument::Compact);
}
const QJsonObject object = document.object();
const QString command = object.value("cmd").toString().trimmed();
if (object.value("protocol").toString() !=
QString::fromLatin1(kRemoteProtocol)) {
return QJsonDocument(ErrorResponse(command, -5,
QStringLiteral("远程控制协议不匹配")))
.toJson(QJsonDocument::Compact);
}
QJsonObject response = BuildServerInfo(command);
if (command == QStringLiteral("select_model")) {
const QString modelType = object.value("modelType")
.toString()
.trimmed()
.toUpper();
if (modelType != QStringLiteral("A320") &&
modelType != QStringLiteral("B737")) {
response = ErrorResponse(command, -2,
QStringLiteral("modelType必须为A320或B737"));
} else if (!m_modelHandler || !m_modelHandler(modelType)) {
response = ErrorResponse(command, -3,
QStringLiteral("App未接受所选机型"));
} else {
response["modelType"] = modelType;
response["message"] = QStringLiteral("机型已同步到App");
}
} else if (command == QStringLiteral("start")) {
const QString modelType = object.value("modelType")
.toString()
.trimmed()
.toUpper();
if (!modelType.isEmpty() &&
modelType != QStringLiteral("A320") &&
modelType != QStringLiteral("B737")) {
response = ErrorResponse(command, -2,
QStringLiteral("modelType必须为空、A320或B737"));
} else if (!m_startHandler || !m_startHandler(modelType)) {
response = ErrorResponse(command, -3,
QStringLiteral("服务端拒绝启动停机引导,请检查设备或安全状态"));
} else {
response["modelType"] = modelType;
response["guidanceRunning"] = true;
}
} else if (command == QStringLiteral("stop")) {
const int result = m_stopHandler ? m_stopHandler() : -1;
if (result != 0) {
response = ErrorResponse(command, result,
QStringLiteral("停止停机引导失败"));
} else {
response["guidanceRunning"] = false;
}
} else if (command == QStringLiteral("get_status")) {
const std::string status = m_statusProvider
? m_statusProvider()
: std::string();
QJsonParseError statusError;
const QJsonDocument statusDocument = QJsonDocument::fromJson(
QByteArray(status.data(), static_cast<int>(status.size())),
&statusError);
if (statusError.error == QJsonParseError::NoError &&
statusDocument.isObject()) {
response["status"] = statusDocument.object();
} else {
response["status"] = QJsonObject();
}
} else if (command != QStringLiteral("get_info") &&
command != QStringLiteral("ping")) {
response = ErrorResponse(command, -4,
QStringLiteral("不支持的控制命令"));
}
return QJsonDocument(response).toJson(QJsonDocument::Compact);
}
void ParkingSpaceGuideRemoteServer::HandleDiscoveryDatagrams()
{
while (m_discoverySocket && m_discoverySocket->hasPendingDatagrams()) {
QByteArray request;
request.resize(static_cast<int>(
m_discoverySocket->pendingDatagramSize()));
QHostAddress sender;
quint16 senderPort = 0;
const qint64 bytes = m_discoverySocket->readDatagram(
request.data(), request.size(), &sender, &senderPort);
if (bytes <= 0) {
continue;
}
request.resize(static_cast<int>(bytes));
QJsonParseError parseError;
const QJsonDocument document = QJsonDocument::fromJson(
request, &parseError);
if (parseError.error != QJsonParseError::NoError ||
!document.isObject()) {
continue;
}
const QJsonObject object = document.object();
if (object.value("cmd").toString() != QStringLiteral("discover")) {
continue;
}
if (object.value("protocol").toString() !=
QString::fromLatin1(kRemoteProtocol)) {
continue;
}
const QString service = object.value("service").toString();
if (!service.isEmpty() &&
service != QString::fromLatin1(kRemoteService)) {
continue;
}
const QByteArray response = QJsonDocument(
BuildServerInfo(QStringLiteral("discover")))
.toJson(QJsonDocument::Compact);
const qint64 sentBytes = m_discoverySocket->writeDatagram(
response, sender, senderPort);
const QByteArray senderText = sender.toString().toUtf8();
if (sentBytes < 0) {
const QByteArray errorText = m_discoverySocket->errorString()
.toUtf8();
LOG_ERROR("ParkingSpaceGuide discovery reply failed peer=%s:%u error=%s\n",
senderText.constData(),
static_cast<unsigned int>(senderPort),
errorText.constData());
} else {
LOG_INFO("ParkingSpaceGuide discovery request peer=%s:%u replyBytes=%lld\n",
senderText.constData(),
static_cast<unsigned int>(senderPort),
static_cast<long long>(sentBytes));
}
if (m_logHandler) {
m_logHandler(sentBytes < 0
? QStringLiteral("远程显示搜索回复失败:%1:%2%3")
.arg(sender.toString())
.arg(senderPort)
.arg(m_discoverySocket->errorString())
: QStringLiteral("收到远程显示搜索:%1:%2已回复")
.arg(sender.toString())
.arg(senderPort));
}
}
}
std::string ParkingSpaceGuideRemoteServer::DeviceId() const
{
const QByteArray uniqueId = QSysInfo::machineUniqueId();
if (!uniqueId.isEmpty()) {
return uniqueId.toHex().toStdString();
}
return QHostInfo::localHostName().toStdString();
}
void ParkingSpaceGuideRemoteServer::SetError(const QString& message)
{
std::lock_guard<std::mutex> lock(m_errorMutex);
m_lastError = message;
}