#include "rpcservicewidget.h" #include "robotclient.h" #include "ui_rpcservicewidget.h" #include #include #include #include #include #include #include #include #ifdef CMVR_WITH_GRPC #include #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::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; } 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); }