368 lines
13 KiB
C++
368 lines
13 KiB
C++
#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;
|
||
}
|