CmvrControlStation/src/rpcservicewidget.cpp

221 lines
8.0 KiB
C++

#include "rpcservicewidget.h"
#include "robotclient.h"
#include "ui_rpcservicewidget.h"
#include <QComboBox>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QTextEdit>
#ifdef CMVR_WITH_GRPC
#include <google/protobuf/descriptor.h>
#endif
namespace {
#ifdef CMVR_WITH_GRPC
QJsonValue defaultFieldValue(const google::protobuf::FieldDescriptor *field, int depth);
QJsonObject messageTemplate(const google::protobuf::Descriptor *descriptor, int depth)
{
QJsonObject object;
if (!descriptor || depth > 5)
return object;
for (int index = 0; index < descriptor->field_count(); ++index) {
const google::protobuf::FieldDescriptor *field = descriptor->field(index);
if (field->containing_oneof() &&
field->containing_oneof()->field(0) != field) {
continue;
}
if (field->name() == "timestamp")
continue;
object.insert(QString::fromStdString(field->json_name()),
field->is_repeated() ? QJsonValue(QJsonArray())
: defaultFieldValue(field, depth + 1));
}
return object;
}
QJsonValue defaultFieldValue(const google::protobuf::FieldDescriptor *field, int depth)
{
using Field = google::protobuf::FieldDescriptor;
switch (field->cpp_type()) {
case Field::CPPTYPE_BOOL:
return false;
case Field::CPPTYPE_INT32:
case Field::CPPTYPE_UINT32:
case Field::CPPTYPE_FLOAT:
case Field::CPPTYPE_DOUBLE:
return 0;
case Field::CPPTYPE_INT64:
case Field::CPPTYPE_UINT64:
return QStringLiteral("0");
case Field::CPPTYPE_ENUM:
return QString::fromStdString(field->enum_type()->value(0)->name());
case Field::CPPTYPE_MESSAGE:
return messageTemplate(field->message_type(), depth);
case Field::CPPTYPE_STRING:
default:
return QString();
}
}
#endif
}
RpcServiceWidget::RpcServiceWidget(const QString &serviceName, RobotClient *client,
QWidget *parent)
: QWidget(parent), m_ui(new Ui::RpcServiceWidget), m_client(client),
m_serviceName(serviceName)
{
m_ui->setupUi(this);
const QString shortName = serviceName.section(QLatin1Char('.'), -1);
m_ui->serviceNameLabel->setText(shortName);
m_ui->servicePathLabel->setText(serviceName);
m_ui->requestEdit->setFontFamily(QStringLiteral("Consolas"));
m_ui->responseEdit->setFontFamily(QStringLiteral("Consolas"));
#ifdef CMVR_WITH_GRPC
const google::protobuf::ServiceDescriptor *service =
google::protobuf::DescriptorPool::generated_pool()->FindServiceByName(
serviceName.toStdString());
if (service) {
for (int index = 0; index < service->method_count(); ++index) {
const google::protobuf::MethodDescriptor *method = service->method(index);
QString kind = QStringLiteral("Unary");
if (method->client_streaming() && method->server_streaming())
kind = QStringLiteral("Bidi stream");
else if (method->client_streaming())
kind = QStringLiteral("Client stream");
else if (method->server_streaming())
kind = QStringLiteral("Server stream");
m_ui->methodCombo->addItem(
QStringLiteral("%1 [%2]").arg(QString::fromStdString(method->name()), kind),
QString::fromStdString(method->name()));
}
}
#endif
if (m_ui->methodCombo->count() == 0) {
m_ui->methodCombo->addItem(QStringLiteral("没有可用的 protobuf 描述符"));
}
m_ui->executeButton->setEnabled(false);
connect(m_ui->methodCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &RpcServiceWidget::updateMethod);
connect(m_ui->resetButton, &QPushButton::clicked,
this, &RpcServiceWidget::resetRequest);
connect(m_ui->executeButton, &QPushButton::clicked,
this, &RpcServiceWidget::executeRequest);
connect(m_client, &RobotClient::rpcFinished,
this, &RpcServiceWidget::handleResult);
connect(m_client, &RobotClient::connectionChanged, this,
[this](bool connected, int, const QString &) {
m_ui->executeButton->setEnabled(connected && m_ui->methodCombo->count() > 0);
});
updateMethod();
}
RpcServiceWidget::~RpcServiceWidget()
{
delete m_ui;
}
void RpcServiceWidget::setDeviceId(const QString &deviceId)
{
m_ui->deviceIdEdit->setText(deviceId);
}
QString RpcServiceWidget::serviceName() const
{
return m_serviceName;
}
QString RpcServiceWidget::requestTemplate(const QString &methodName) const
{
#ifdef CMVR_WITH_GRPC
const google::protobuf::ServiceDescriptor *service =
google::protobuf::DescriptorPool::generated_pool()->FindServiceByName(
m_serviceName.toStdString());
const google::protobuf::MethodDescriptor *method =
service ? service->FindMethodByName(methodName.toStdString()) : nullptr;
if (method) {
const QJsonObject object = messageTemplate(method->input_type(), 0);
return QString::fromUtf8(QJsonDocument(object).toJson(QJsonDocument::Indented));
}
#else
Q_UNUSED(methodName)
#endif
return QStringLiteral("{}");
}
void RpcServiceWidget::updateMethod()
{
const QString methodName = m_ui->methodCombo->currentData().toString();
if (methodName.isEmpty())
return;
#ifdef CMVR_WITH_GRPC
const google::protobuf::ServiceDescriptor *service =
google::protobuf::DescriptorPool::generated_pool()->FindServiceByName(
m_serviceName.toStdString());
const google::protobuf::MethodDescriptor *method =
service ? service->FindMethodByName(methodName.toStdString()) : nullptr;
if (method) {
QString mode = QStringLiteral("Unary RPC");
if (method->client_streaming() && method->server_streaming())
mode = QStringLiteral("双向流:请求可填写 JSON 数组,最多接收 100 条反馈");
else if (method->client_streaming())
mode = QStringLiteral("客户端流:请求填写 JSON 数组");
else if (method->server_streaming())
mode = QStringLiteral("服务端流:执行后接收最多 5 秒或 100 条反馈");
m_ui->methodDetailLabel->setText(
QStringLiteral("%1 -> %2 %3")
.arg(QString::fromStdString(method->input_type()->full_name()),
QString::fromStdString(method->output_type()->full_name()), mode));
m_ui->executeButton->setText(method->client_streaming() || method->server_streaming()
? QStringLiteral("执行流式 RPC")
: QStringLiteral("执行 RPC"));
}
#endif
resetRequest();
}
void RpcServiceWidget::resetRequest()
{
const QString methodName = m_ui->methodCombo->currentData().toString();
m_ui->requestEdit->setPlainText(requestTemplate(methodName));
m_ui->responseEdit->clear();
m_ui->resultLabel->setText(QStringLiteral("等待执行"));
}
void RpcServiceWidget::executeRequest()
{
const QString methodName = m_ui->methodCombo->currentData().toString();
if (methodName.isEmpty())
return;
m_pendingMethod = methodName;
m_ui->executeButton->setEnabled(false);
m_ui->resultLabel->setText(QStringLiteral("正在调用..."));
m_ui->responseEdit->clear();
m_client->requestRpc(m_serviceName, methodName,
m_ui->requestEdit->toPlainText(),
m_ui->deviceIdEdit->text().trimmed());
}
void RpcServiceWidget::handleResult(const QString &serviceName, const QString &methodName,
bool success, const QString &responseJson)
{
if (serviceName != m_serviceName || methodName != m_pendingMethod)
return;
m_pendingMethod.clear();
m_ui->executeButton->setEnabled(true);
m_ui->resultLabel->setText(success ? QStringLiteral("调用成功")
: QStringLiteral("调用失败"));
m_ui->responseEdit->setPlainText(responseJson);
}