feat: add device inventory and move arm JSON RPC

This commit is contained in:
xtkuang 2026-08-04 09:52:51 +08:00
parent 490c141ca8
commit b4ec07c851
13 changed files with 1019 additions and 47 deletions

View File

@ -70,8 +70,9 @@ file(GLOB_RECURSE PROTO_FILES ${PROTO_IMPORT_DIR}/*.proto)
set(Protobuf_PROTOC_EXECUTABLE "${CMAKE_INSTALL_PREFIX}/bin/protoc" CACHE FILEPATH "" FORCE) set(Protobuf_PROTOC_EXECUTABLE "${CMAKE_INSTALL_PREFIX}/bin/protoc" CACHE FILEPATH "" FORCE)
set_property(TARGET gRPC::grpc_cpp_plugin set_target_properties(gRPC::grpc_cpp_plugin PROPERTIES
PROPERTY IMPORTED_LOCATION "${CMAKE_INSTALL_PREFIX}/bin/grpc_cpp_plugin" IMPORTED_LOCATION "${CMAKE_INSTALL_PREFIX}/bin/grpc_cpp_plugin"
IMPORTED_LOCATION_RELEASE "${CMAKE_INSTALL_PREFIX}/bin/grpc_cpp_plugin"
) )
# 1) OBJECT/ pb.cc # 1) OBJECT/ pb.cc

View File

@ -2,8 +2,10 @@
`AuboArm` 是 AUBO SDK v0.27.1 的 `RobotArm` 后端。控制柜 Standard 数字 IO `AuboArm` 是 AUBO SDK v0.27.1 的 `RobotArm` 后端。控制柜 Standard 数字 IO
通过设备通用的 `executeJsonCommand` 接口访问,远程调用复用 通过设备通用的 `executeJsonCommand` 接口访问,远程调用复用
`cmvr.api.SystemService/ExecuteJsonCommand`,不经过 `ArmService` `cmvr.api.ArmService/ExecuteJsonCommand`,不经过 `SystemService`
`MotorService` `MotorService`。该 RPC 只路由到 `RobotArm`,不会把 JSON 命令转发给其他设备类型。
旧的 `cmvr.api.SystemService/ExecuteJsonCommand` 不再注册,调用方必须更新服务路径;
请求和响应消息结构保持不变。
返回 [Devices 模块指南](../../README.md) 或 [项目总览](../../../../README.md)。 返回 [Devices 模块指南](../../README.md) 或 [项目总览](../../../../README.md)。
@ -14,9 +16,9 @@
- 设备配置:[`../../../config/devices/arm/aubo_arm.pb.txt`](../../../config/devices/arm/aubo_arm.pb.txt) - 设备配置:[`../../../config/devices/arm/aubo_arm.pb.txt`](../../../config/devices/arm/aubo_arm.pb.txt)
- DeviceManager 配置: - DeviceManager 配置:
[`../../../config/manager/device_manager.pb.txt`](../../../config/manager/device_manager.pb.txt) [`../../../config/manager/device_manager.pb.txt`](../../../config/manager/device_manager.pb.txt)
- SystemService 实现: - ArmService 实现:
[`../../../service/grpc/src/grpc_system_service.cpp`](../../../service/grpc/src/grpc_system_service.cpp) [`../../../service/grpc/src/grpc_arm_service.cpp`](../../../service/grpc/src/grpc_arm_service.cpp)
- Proto[`../../../../protos/cmvr/api/system_service.proto`](../../../../protos/cmvr/api/system_service.proto) - Proto[`../../../../protos/cmvr/api/arm_service.proto`](../../../../protos/cmvr/api/arm_service.proto)
仓库配置使用 SDK RPC 端口 `30004`。现场部署必须填写真实控制器地址和凭据, 仓库配置使用 SDK RPC 端口 `30004`。现场部署必须填写真实控制器地址和凭据,
不要把生产密码提交到默认配置。 不要把生产密码提交到默认配置。
@ -68,7 +70,7 @@ grpcurl -plaintext \
"requestJson":"{\"command\":\"cabinet_io\",\"operation\":\"get_di\",\"index\":0}" "requestJson":"{\"command\":\"cabinet_io\",\"operation\":\"get_di\",\"index\":0}"
}' \ }' \
127.0.0.1:50052 \ 127.0.0.1:50052 \
cmvr.api.SystemService/ExecuteJsonCommand cmvr.api.ArmService/ExecuteJsonCommand
``` ```
设置 DO0 为高电平: 设置 DO0 为高电平:
@ -80,7 +82,7 @@ grpcurl -plaintext \
"requestJson":"{\"command\":\"cabinet_io\",\"operation\":\"set_do\",\"index\":0,\"value\":true}" "requestJson":"{\"command\":\"cabinet_io\",\"operation\":\"set_do\",\"index\":0,\"value\":true}"
}' \ }' \
127.0.0.1:50052 \ 127.0.0.1:50052 \
cmvr.api.SystemService/ExecuteJsonCommand cmvr.api.ArmService/ExecuteJsonCommand
``` ```
使用源码默认配置时: 使用源码默认配置时:

View File

@ -47,6 +47,63 @@ if(BUILD_TESTING)
) )
set_tests_properties(grpc_camera_stream_policy_test PROPERTIES TIMEOUT 10) set_tests_properties(grpc_camera_stream_policy_test PROPERTIES TIMEOUT 10)
add_executable(grpc_system_service_test
grpc/tests/grpc_system_service_test.cpp
)
target_include_directories(grpc_system_service_test
PRIVATE
${CMAKE_SOURCE_DIR}/cmvr-es
${CMAKE_SOURCE_DIR}/cmvr-es/manager/device_manager
)
target_link_libraries(grpc_system_service_test
PRIVATE
service
cmvr_es::proto
gtest
gtest_main
pthread
)
add_test(
NAME grpc_system_service_test
COMMAND grpc_system_service_test
)
set(_grpc_system_test_environment
"LD_LIBRARY_PATH=${CMVR_TEST_EXTERNAL_LIBRARY_PATH}"
)
if(CMVR_TEST_SYSTEM_LIBSTDCXX)
list(APPEND _grpc_system_test_environment
"LD_PRELOAD=${CMVR_TEST_SYSTEM_LIBSTDCXX}")
endif()
set_tests_properties(grpc_system_service_test PROPERTIES
TIMEOUT 10
ENVIRONMENT "${_grpc_system_test_environment}"
)
add_executable(grpc_arm_service_test
grpc/tests/grpc_arm_service_test.cpp
)
target_include_directories(grpc_arm_service_test
PRIVATE
${CMAKE_SOURCE_DIR}/cmvr-es
${CMAKE_SOURCE_DIR}/cmvr-es/manager/device_manager
)
target_link_libraries(grpc_arm_service_test
PRIVATE
service
cmvr_es::proto
gtest
gtest_main
pthread
)
add_test(
NAME grpc_arm_service_test
COMMAND grpc_arm_service_test
)
set_tests_properties(grpc_arm_service_test PROPERTIES
TIMEOUT 10
ENVIRONMENT "${_grpc_system_test_environment}"
)
add_executable(grpc_arm_teleop_service_test add_executable(grpc_arm_teleop_service_test
grpc/tests/grpc_arm_teleop_service_test.cpp grpc/tests/grpc_arm_teleop_service_test.cpp
) )

View File

@ -21,6 +21,24 @@ gRPC 和 QUIC 的职责边界:
- 实时音视频使用 QUIC DATAGRAM - 实时音视频使用 QUIC DATAGRAM
- `quic_edge/` 不是平台 Gateway也不是浏览器服务器。 - `quic_edge/` 不是平台 Gateway也不是浏览器服务器。
## SystemService 设备清单
`SystemService/GetDeviceList` 返回 `DeviceManager` 的当前只读快照,只包含
`enabled=true` 的设备。启用但创建、初始化、启动或健康检查失败的设备仍会返回,
并通过 `manager_state`、`health`、`has_error` 和 `error_message` 描述异常。
接口同时返回稳定的 `device_type` 和仅用于展示/诊断的具体 `type_name`;调用方
不得使用 `type_name` 做设备类别判断。
该 RPC 不修改配置、不动态注册设备,也不触发设备生命周期操作。启用 reflection
后可直接查询:
```bash
grpcurl -plaintext \
-d '{}' \
127.0.0.1:50052 \
cmvr.api.SystemService/GetDeviceList
```
## MotorService ## MotorService
`MotorService` 将 gRPC 电机命令适配到已经由 `DeviceManager` 创建的 `MotorService` 将 gRPC 电机命令适配到已经由 `DeviceManager` 创建的
@ -53,9 +71,11 @@ PLC 后端的连接 epoch、stream epoch、寄存器、ACK 和 TIA Portal 要求
- [Modbus TCP PLC runtime](../devices/motor/bus_runtime/modbus_tcp/README.md) - [Modbus TCP PLC runtime](../devices/motor/bus_runtime/modbus_tcp/README.md)
- [MotorService 与 CMVR PLC v1 完整协议](../../docs/motor_service_modbus_tcp.md) - [MotorService 与 CMVR PLC v1 完整协议](../../docs/motor_service_modbus_tcp.md)
AUBO 控制柜 IO 不经过 `MotorService``ArmService`,而是复用 AUBO 控制柜 IO 不经过 `MotorService`,由
`SystemService/ExecuteJsonCommand`。厂商命令和安全约束见 `ArmService/ExecuteJsonCommand` 转发到目标 `RobotArm`。厂商命令和安全约束见
[AUBO 控制柜 IO](../devices/arm/aubo_arm/README.md)。 [AUBO 控制柜 IO](../devices/arm/aubo_arm/README.md)。
旧的 `SystemService/ExecuteJsonCommand` 已移除;相机 PTZ 应使用类型化的
`CameraService/ControlPtz`
## 新增 gRPC Service ## 新增 gRPC Service

View File

@ -50,6 +50,9 @@ public:
grpc::Status computeForwardKinematics(grpc::ServerContext* context, grpc::Status computeForwardKinematics(grpc::ServerContext* context,
const api::ComputeForwardKinematics_Request* request, const api::ComputeForwardKinematics_Request* request,
api::ComputeForwardKinematics_Response* response) override; api::ComputeForwardKinematics_Response* response) override;
grpc::Status ExecuteJsonCommand(grpc::ServerContext* context,
const api::JsonDeviceCommand_Request* request,
api::JsonDeviceCommand_Feedback* response) override;
grpc::Status clearFault(grpc::ServerContext *context, grpc::Status clearFault(grpc::ServerContext *context,
const cmvr::api::CommandHeader_Request *request, const cmvr::api::CommandHeader_Request *request,
cmvr::api::CommandHeader_Feedback *response) override; cmvr::api::CommandHeader_Feedback *response) override;

View File

@ -17,8 +17,8 @@ namespace cmvr::service
~gRPCSystemServiceImpl() override = default; ~gRPCSystemServiceImpl() override = default;
grpc::Status GetSystemInfo(grpc::ServerContext* context, const api::GetSystemInfoCommand_Request* request, api::GetSystemInfoCommand_Feedback* response) override; grpc::Status GetSystemInfo(grpc::ServerContext* context, const api::GetSystemInfoCommand_Request* request, api::GetSystemInfoCommand_Feedback* response) override;
grpc::Status GetSystemStatus(grpc::ServerContext* context, const api::GetSystemStatusCommand_Request* request, api::GetSystemStatusCommand_Feedback* response) override; grpc::Status GetSystemStatus(grpc::ServerContext* context, const api::GetSystemStatusCommand_Request* request, api::GetSystemStatusCommand_Feedback* response) override;
grpc::Status GetDeviceList(grpc::ServerContext* context, const api::GetDeviceListCommand_Request* request, api::GetDeviceListCommand_Feedback* response) override;
grpc::Status UpdateParams(grpc::ServerContext* context, const cmvr::api::UpdateParamsCommand_Request* request, cmvr::api::UpdateParamsCommand_Feedback* response) override; grpc::Status UpdateParams(grpc::ServerContext* context, const cmvr::api::UpdateParamsCommand_Request* request, cmvr::api::UpdateParamsCommand_Feedback* response) override;
grpc::Status ExecuteJsonCommand(grpc::ServerContext* context, const cmvr::api::JsonDeviceCommand_Request* request, cmvr::api::JsonDeviceCommand_Feedback* response) override;
grpc::Status StopAll(grpc::ServerContext* context, const cmvr::api::StopAllCommand_Request* request, cmvr::api::StopAllCommand_Feedback* response) override; grpc::Status StopAll(grpc::ServerContext* context, const cmvr::api::StopAllCommand_Request* request, cmvr::api::StopAllCommand_Feedback* response) override;
private: private:
device::DeviceManager& dmgr_; device::DeviceManager& dmgr_;

View File

@ -510,6 +510,40 @@ grpc::Status gRPCArmServiceImpl::computeForwardKinematics(grpc::ServerContext*,
return grpc::Status(grpc::StatusCode::UNIMPLEMENTED, "computeForwardKinematics is not implemented"); return grpc::Status(grpc::StatusCode::UNIMPLEMENTED, "computeForwardKinematics is not implemented");
} }
grpc::Status gRPCArmServiceImpl::ExecuteJsonCommand(
grpc::ServerContext*,
const api::JsonDeviceCommand_Request* request,
api::JsonDeviceCommand_Feedback* response)
{
try {
const std::string device_id = request->header().device_id();
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
if (!arm) {
fillFeedback(
response->mutable_header(),
false,
"Device not found: " + device_id);
return grpc::Status::OK;
}
std::string response_json;
const bool success = arm->executeJsonCommand(
request->request_json(), response_json);
fillFeedback(
response->mutable_header(),
success,
success ? "" : response_json);
response->set_response_json(response_json);
if (success) {
logRpcSuccess("ExecuteJsonCommand", device_id);
}
return grpc::Status::OK;
} catch (const std::exception& e) {
fillFeedback(response->mutable_header(), false, e.what());
return grpc::Status::OK;
}
}
grpc::Status gRPCArmServiceImpl::clearFault(grpc::ServerContext *context, grpc::Status gRPCArmServiceImpl::clearFault(grpc::ServerContext *context,
const cmvr::api::CommandHeader_Request *request, const cmvr::api::CommandHeader_Request *request,
cmvr::api::CommandHeader_Feedback *response) cmvr::api::CommandHeader_Feedback *response)

View File

@ -4,12 +4,107 @@
#include "../include/grpc_system_service.h" #include "../include/grpc_system_service.h"
#include <chrono>
#include <cstdint>
#include "common/base/logging/logger.h" #include "common/base/logging/logger.h"
using namespace cmvr::device;
using namespace cmvr::device; using namespace cmvr::device;
using namespace cmvr::service; using namespace cmvr::service;
namespace {
std::uint64_t unixTimeMs() noexcept
{
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch());
return elapsed.count() > 0
? static_cast<std::uint64_t>(elapsed.count())
: 0U;
}
cmvr::api::SystemDeviceType toApiDeviceType(
const cmvr::device::DeviceKind kind) noexcept
{
switch (kind) {
case cmvr::device::DeviceKind::AGV:
return cmvr::api::SYSTEM_DEVICE_TYPE_AGV;
case cmvr::device::DeviceKind::Arm:
return cmvr::api::SYSTEM_DEVICE_TYPE_ARM;
case cmvr::device::DeviceKind::Battery:
return cmvr::api::SYSTEM_DEVICE_TYPE_BATTERY;
case cmvr::device::DeviceKind::BioHead:
return cmvr::api::SYSTEM_DEVICE_TYPE_BIO_HEAD;
case cmvr::device::DeviceKind::Camera:
return cmvr::api::SYSTEM_DEVICE_TYPE_CAMERA;
case cmvr::device::DeviceKind::CanBus:
return cmvr::api::SYSTEM_DEVICE_TYPE_CAN_BUS;
case cmvr::device::DeviceKind::DexHand:
return cmvr::api::SYSTEM_DEVICE_TYPE_DEX_HAND;
case cmvr::device::DeviceKind::Gripper:
return cmvr::api::SYSTEM_DEVICE_TYPE_GRIPPER;
case cmvr::device::DeviceKind::Microphone:
return cmvr::api::SYSTEM_DEVICE_TYPE_MICROPHONE;
case cmvr::device::DeviceKind::Motor:
return cmvr::api::SYSTEM_DEVICE_TYPE_MOTOR;
case cmvr::device::DeviceKind::MotorSystem:
return cmvr::api::SYSTEM_DEVICE_TYPE_MOTOR_SYSTEM;
case cmvr::device::DeviceKind::MujocoViewer:
return cmvr::api::SYSTEM_DEVICE_TYPE_MUJOCO_VIEWER;
case cmvr::device::DeviceKind::MujocoWorld:
return cmvr::api::SYSTEM_DEVICE_TYPE_MUJOCO_WORLD;
case cmvr::device::DeviceKind::Robot:
return cmvr::api::SYSTEM_DEVICE_TYPE_ROBOT;
case cmvr::device::DeviceKind::Speaker:
return cmvr::api::SYSTEM_DEVICE_TYPE_SPEAKER;
case cmvr::device::DeviceKind::Unknown:
break;
}
return cmvr::api::SYSTEM_DEVICE_TYPE_UNSPECIFIED;
}
cmvr::api::SystemDeviceState toApiDeviceState(
const cmvr::device::ManagedDeviceState state) noexcept
{
switch (state) {
case cmvr::device::ManagedDeviceState::Disabled:
return cmvr::api::SYSTEM_DEVICE_STATE_DISABLED;
case cmvr::device::ManagedDeviceState::Initializing:
return cmvr::api::SYSTEM_DEVICE_STATE_INITIALIZING;
case cmvr::device::ManagedDeviceState::Registered:
return cmvr::api::SYSTEM_DEVICE_STATE_REGISTERED;
case cmvr::device::ManagedDeviceState::Ready:
return cmvr::api::SYSTEM_DEVICE_STATE_READY;
case cmvr::device::ManagedDeviceState::Running:
return cmvr::api::SYSTEM_DEVICE_STATE_RUNNING;
case cmvr::device::ManagedDeviceState::Stopped:
return cmvr::api::SYSTEM_DEVICE_STATE_STOPPED;
case cmvr::device::ManagedDeviceState::Error:
return cmvr::api::SYSTEM_DEVICE_STATE_ERROR;
case cmvr::device::ManagedDeviceState::Unknown:
break;
}
return cmvr::api::SYSTEM_DEVICE_STATE_UNSPECIFIED;
}
cmvr::api::SystemDeviceHealth toApiDeviceHealth(
const cmvr::device::DeviceHealthState state) noexcept
{
switch (state) {
case cmvr::device::DeviceHealthState::Healthy:
return cmvr::api::SYSTEM_DEVICE_HEALTH_HEALTHY;
case cmvr::device::DeviceHealthState::Degraded:
return cmvr::api::SYSTEM_DEVICE_HEALTH_DEGRADED;
case cmvr::device::DeviceHealthState::Fault:
return cmvr::api::SYSTEM_DEVICE_HEALTH_FAULT;
case cmvr::device::DeviceHealthState::Unknown:
break;
}
return cmvr::api::SYSTEM_DEVICE_HEALTH_UNSPECIFIED;
}
} // namespace
gRPCSystemServiceImpl::gRPCSystemServiceImpl(): dmgr_(DeviceManager::getInstance()) {} gRPCSystemServiceImpl::gRPCSystemServiceImpl(): dmgr_(DeviceManager::getInstance()) {}
grpc::Status gRPCSystemServiceImpl::GetSystemInfo(grpc::ServerContext* context, grpc::Status gRPCSystemServiceImpl::GetSystemInfo(grpc::ServerContext* context,
@ -83,6 +178,55 @@ grpc::Status gRPCSystemServiceImpl::GetSystemStatus(grpc::ServerContext* context
} }
} }
grpc::Status gRPCSystemServiceImpl::GetDeviceList(
grpc::ServerContext* context,
const api::GetDeviceListCommand_Request* request,
api::GetDeviceListCommand_Feedback* response)
{
(void)context;
(void)request;
try {
const auto snapshot = dmgr_.snapshot();
response->set_manager_name(snapshot.name);
response->set_manager_version(snapshot.version);
response->set_manager_description(snapshot.description);
response->set_sampled_at_unix_ms(unixTimeMs());
for (const auto& source : snapshot.devices) {
// The public inventory contains enabled entries only. Keep enabled
// devices visible even when their lifecycle or health is in error.
if (!source.enabled) {
continue;
}
auto* destination = response->add_device_list();
destination->set_device_id(source.id);
destination->set_device_type(toApiDeviceType(source.kind));
destination->set_type_name(source.type_name);
destination->set_enabled(true);
destination->set_manager_state(toApiDeviceState(source.state));
destination->set_health(toApiDeviceHealth(source.health.state));
destination->set_has_error(source.abnormal);
destination->set_error_message(source.error_message);
destination->set_status_updated_at_unix_ms(
source.status_updated_at_unix_ms);
}
response->mutable_header()->set_success(true);
setCurrentTimestamp(response->mutable_header()->mutable_timestamp());
CMVR_LOG(DEBUG) << "[gRPCSystemServiceImpl] (GetDeviceList): success, devices="
<< response->device_list_size();
return grpc::Status::OK;
}
catch (const std::exception& e) {
response->Clear();
response->mutable_header()->set_success(false);
response->mutable_header()->set_error_message(e.what());
setCurrentTimestamp(response->mutable_header()->mutable_timestamp());
return grpc::Status::OK;
}
}
grpc::Status gRPCSystemServiceImpl::UpdateParams(grpc::ServerContext* context, const cmvr::api::UpdateParamsCommand_Request* request, cmvr::api::UpdateParamsCommand_Feedback* response) grpc::Status gRPCSystemServiceImpl::UpdateParams(grpc::ServerContext* context, const cmvr::api::UpdateParamsCommand_Request* request, cmvr::api::UpdateParamsCommand_Feedback* response)
{ {
response->mutable_header()->set_success(false); response->mutable_header()->set_success(false);
@ -91,37 +235,6 @@ grpc::Status gRPCSystemServiceImpl::UpdateParams(grpc::ServerContext* context, c
return grpc::Status::OK; return grpc::Status::OK;
} }
grpc::Status gRPCSystemServiceImpl::ExecuteJsonCommand(grpc::ServerContext* context,
const cmvr::api::JsonDeviceCommand_Request* request, cmvr::api::JsonDeviceCommand_Feedback* response)
{
try {
const std::string& dev_id = request->header().device_id();
auto dev = dmgr_.getDeviceBase(dev_id);
if (!dev) {
response->mutable_header()->set_success(false);
response->mutable_header()->set_error_message("Device not found: " + dev_id);
setCurrentTimestamp(response->mutable_header()->mutable_timestamp());
return grpc::Status::OK;
}
std::string response_json;
const bool success = dev->executeJsonCommand(request->request_json(), response_json);
response->mutable_header()->set_success(success);
if (!success) {
response->mutable_header()->set_error_message(response_json);
}
response->set_response_json(response_json);
setCurrentTimestamp(response->mutable_header()->mutable_timestamp());
return grpc::Status::OK;
}
catch (std::exception& e) {
response->mutable_header()->set_success(false);
response->mutable_header()->set_error_message(e.what());
setCurrentTimestamp(response->mutable_header()->mutable_timestamp());
return grpc::Status::OK;
}
}
grpc::Status gRPCSystemServiceImpl::StopAll(grpc::ServerContext* context, grpc::Status gRPCSystemServiceImpl::StopAll(grpc::ServerContext* context,
const cmvr::api::StopAllCommand_Request* request, cmvr::api::StopAllCommand_Feedback* response) const cmvr::api::StopAllCommand_Request* request, cmvr::api::StopAllCommand_Feedback* response)
{ {

View File

@ -0,0 +1,378 @@
#include "service/grpc/include/grpc_arm_service.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <google/protobuf/descriptor.h>
#include <grpcpp/grpcpp.h>
#include <gtest/gtest.h>
#include "cmvr/config/device_manager_config/device_manager_config.pb.h"
#include "manager/device_manager/include/device_manager.h"
namespace cmvr::service {
namespace {
class JsonCommandRobotArm final : public device::RobotArm {
public:
explicit JsonCommandRobotArm(std::string id)
{
id_ = std::move(id);
}
std::string typeName() const override { return "JsonCommandRobotArm"; }
bool executeJsonCommand(const std::string& request_json,
std::string& response_json) override
{
++execute_calls;
last_request_json = request_json;
response_json = next_response_json;
return next_success;
}
device::RobotModel getRobotModel() const override { return {}; }
std::size_t getDof() const override { return 0U; }
device::ArmState getRobotState() const override { return {}; }
device::JointGroupState getJointState() const override { return {}; }
device::CartesianPose getTcpPose(
device::FrameType = device::FrameType::Base) const override
{
return {};
}
device::RobotMode getRobotMode() const override
{
return device::RobotMode::Unknown;
}
device::SafetyMode getSafetyMode() const override
{
return device::SafetyMode::Unknown;
}
device::ControlMode getControlMode() const override
{
return device::ControlMode::None;
}
device::Result torqueOn() override { return device::Result::success(); }
device::Result torqueOff() override { return device::Result::success(); }
device::Result calibrateZeroQ(const std::string&) override
{
return device::Result::success();
}
device::Result emergencyStop() override
{
return device::Result::success();
}
device::Result protectiveStop() override
{
return device::Result::success();
}
device::Result setSpeedScaling(double) override
{
return device::Result::success();
}
double getSpeedScaling() const override { return 1.0; }
bool isProtectiveStopped() const override { return false; }
bool isEmergencyStopped() const override { return false; }
bool isFault() const override { return false; }
device::Result moveJ(const device::JointPositionCommand&,
const device::MotionOptions&) override
{
return device::Result::success();
}
device::Result speedJ(const device::JointVelocityCommand&,
double,
double) override
{
return device::Result::success();
}
device::Result stopJ(double) override
{
return device::Result::success();
}
device::Result moveL(
const device::CartesianPose&,
const device::MotionOptions&,
device::FrameType = device::FrameType::Base) override
{
return device::Result::success();
}
device::Result speedL(
const device::CartesianVelocity&,
double,
double,
device::FrameType = device::FrameType::Base) override
{
return device::Result::success();
}
device::Result stopL(std::optional<double> = std::nullopt) override
{
return device::Result::success();
}
device::Result stopMotion() override
{
return device::Result::success();
}
device::Result startServoMode(const device::ServoOptions&) override
{
return device::Result::success();
}
device::Result servoJ(const device::JointPositionCommand&) override
{
return device::Result::success();
}
device::Result servoL(
const device::CartesianPose&,
device::FrameType = device::FrameType::Base) override
{
return device::Result::success();
}
device::Result servoSpeedJ(const device::JointVelocityCommand&) override
{
return device::Result::success();
}
device::Result servoSpeedL(
const device::CartesianVelocity&,
device::FrameType = device::FrameType::Base) override
{
return device::Result::success();
}
device::Result stopServoMode() override
{
return device::Result::success();
}
device::Result connect(const std::string&, int) override
{
return device::Result::success();
}
device::Result disconnect() override
{
return device::Result::success();
}
bool isConnected() const override { return true; }
device::Result powerOn() override { return device::Result::success(); }
device::Result powerOff() override { return device::Result::success(); }
device::Result brakeRelease() override
{
return device::Result::success();
}
device::Result shutdown() override
{
return device::Result::success();
}
device::Result clearFault() override
{
return device::Result::success();
}
device::Result unlockProtectiveStop() override
{
return device::Result::success();
}
device::Result loadProgram(const std::string&) override
{
return device::Result::success();
}
device::Result playProgram() override
{
return device::Result::success();
}
device::Result pauseProgram() override
{
return device::Result::success();
}
device::Result stopProgram() override
{
return device::Result::success();
}
std::vector<double> ik(const std::string&,
const std::string&,
const device::CartesianPose&) override
{
return {};
}
std::shared_ptr<cmvr::IKSolver> kinematicsSolver() const override
{
return nullptr;
}
device::CartesianPose fk(const std::string&,
const std::string&) override
{
return {};
}
device::CartesianPose fk(bool = true) override { return {}; }
device::CartesianVelocity getSpeedLCommandTwistBase() const override
{
return {};
}
bool busy() const override { return false; }
int execute_calls{0};
bool next_success{true};
std::string next_response_json;
std::string last_request_json;
};
class JsonCommandNonArmDevice final : public device::AbstractDevice {
public:
explicit JsonCommandNonArmDevice(std::string id)
: AbstractDevice(std::move(id))
{
}
std::string typeName() const override { return "JsonCommandNonArmDevice"; }
bool executeJsonCommand(const std::string&,
std::string& response_json) override
{
++execute_calls;
response_json = R"({"success":true})";
return true;
}
int execute_calls{0};
};
class GrpcArmServiceTest : public ::testing::Test {
protected:
void SetUp() override
{
device::DeviceManager::destroyInstance();
config::DeviceManagerConfig config;
auto& manager = device::DeviceManager::getInstance(config);
left_arm_ = std::make_shared<JsonCommandRobotArm>("left_arm");
aubo_arm_ = std::make_shared<JsonCommandRobotArm>("aubo_arm");
non_arm_ = std::make_shared<JsonCommandNonArmDevice>("camera");
manager.registerDevice(left_arm_);
manager.registerDevice(aubo_arm_);
manager.registerDevice(non_arm_);
service_ = std::make_unique<gRPCArmServiceImpl>();
}
void TearDown() override
{
service_.reset();
non_arm_.reset();
aubo_arm_.reset();
left_arm_.reset();
device::DeviceManager::destroyInstance();
}
grpc::Status execute(const std::string& device_id,
const std::string& request_json,
api::JsonDeviceCommand_Feedback& response)
{
api::JsonDeviceCommand_Request request;
request.mutable_header()->set_device_id(device_id);
request.set_request_json(request_json);
grpc::ServerContext context;
return service_->ExecuteJsonCommand(&context, &request, &response);
}
std::shared_ptr<JsonCommandRobotArm> left_arm_;
std::shared_ptr<JsonCommandRobotArm> aubo_arm_;
std::shared_ptr<JsonCommandNonArmDevice> non_arm_;
std::unique_ptr<gRPCArmServiceImpl> service_;
};
TEST(GrpcArmServiceDescriptorTest,
ExecuteJsonCommandBelongsOnlyToArmService)
{
const auto* pool = google::protobuf::DescriptorPool::generated_pool();
const auto* arm_service =
pool->FindServiceByName("cmvr.api.ArmService");
const auto* system_service =
pool->FindServiceByName("cmvr.api.SystemService");
ASSERT_NE(arm_service, nullptr);
ASSERT_NE(system_service, nullptr);
EXPECT_NE(arm_service->FindMethodByName("ExecuteJsonCommand"), nullptr);
EXPECT_EQ(system_service->FindMethodByName("ExecuteJsonCommand"), nullptr);
}
TEST_F(GrpcArmServiceTest, RoutesByHeaderDeviceIdAndForwardsSuccessfulJson)
{
const std::string request_json =
R"({"command":"cabinet_io","operation":"get_di","index":0})";
const std::string response_json =
R"({"success":true,"operation":"get_di","index":0,"value":false})";
aubo_arm_->next_response_json = response_json;
api::JsonDeviceCommand_Feedback response;
const auto status = execute("aubo_arm", request_json, response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_TRUE(response.header().success());
EXPECT_TRUE(response.header().error_message().empty());
EXPECT_TRUE(response.header().has_timestamp());
EXPECT_GT(response.header().timestamp().seconds(), 0);
EXPECT_EQ(response.response_json(), response_json);
EXPECT_EQ(aubo_arm_->execute_calls, 1);
EXPECT_EQ(aubo_arm_->last_request_json, request_json);
EXPECT_EQ(left_arm_->execute_calls, 0);
}
TEST_F(GrpcArmServiceTest, ForwardsDeviceJsonFailureWithLegacyGrpcOkSemantics)
{
const std::string response_json =
R"({"success":false,"error_code":"not_connected"})";
aubo_arm_->next_success = false;
aubo_arm_->next_response_json = response_json;
api::JsonDeviceCommand_Feedback response;
const auto status = execute(
"aubo_arm",
R"({"command":"cabinet_io","operation":"get_do","index":0})",
response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_FALSE(response.header().success());
EXPECT_EQ(response.header().error_message(), response_json);
EXPECT_TRUE(response.header().has_timestamp());
EXPECT_GT(response.header().timestamp().seconds(), 0);
EXPECT_EQ(response.response_json(), response_json);
EXPECT_EQ(aubo_arm_->execute_calls, 1);
EXPECT_EQ(left_arm_->execute_calls, 0);
}
TEST_F(GrpcArmServiceTest,
MissingOrNonArmIdReturnsBusinessFailureWithoutBackendDispatch)
{
api::JsonDeviceCommand_Feedback non_arm_response;
const auto non_arm_status = execute(
"camera", R"({"command":"cabinet_io"})", non_arm_response);
ASSERT_TRUE(non_arm_status.ok()) << non_arm_status.error_message();
EXPECT_FALSE(non_arm_response.header().success());
EXPECT_EQ(non_arm_response.header().error_message(),
"Device not found: camera");
EXPECT_TRUE(non_arm_response.header().has_timestamp());
EXPECT_TRUE(non_arm_response.response_json().empty());
EXPECT_EQ(non_arm_->execute_calls, 0);
EXPECT_EQ(aubo_arm_->execute_calls, 0);
EXPECT_EQ(left_arm_->execute_calls, 0);
api::JsonDeviceCommand_Feedback missing_response;
const auto missing_status = execute(
"missing_arm", R"({"command":"cabinet_io"})", missing_response);
ASSERT_TRUE(missing_status.ok()) << missing_status.error_message();
EXPECT_FALSE(missing_response.header().success());
EXPECT_EQ(missing_response.header().error_message(),
"Device not found: missing_arm");
EXPECT_TRUE(missing_response.header().has_timestamp());
EXPECT_TRUE(missing_response.response_json().empty());
EXPECT_EQ(non_arm_->execute_calls, 0);
EXPECT_EQ(aubo_arm_->execute_calls, 0);
EXPECT_EQ(left_arm_->execute_calls, 0);
}
} // namespace
} // namespace cmvr::service

View File

@ -0,0 +1,291 @@
#include "service/grpc/include/grpc_system_service.h"
#include <array>
#include <chrono>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <grpcpp/grpcpp.h>
#include <gtest/gtest.h>
#include "cmvr/config/device_manager_config/device_manager_config.pb.h"
#include "manager/device_manager/include/device_manager.h"
namespace cmvr::service {
namespace {
class SnapshotDevice final : public device::AbstractDevice {
public:
SnapshotDevice(std::string id,
device::DeviceKind kind,
std::string type_name,
device::DeviceHealthSnapshot health = {})
: AbstractDevice(std::move(id)),
kind_(kind),
type_name_(std::move(type_name)),
health_(std::move(health))
{
}
device::DeviceKind kind() const noexcept override { return kind_; }
std::string typeName() const override { return type_name_; }
device::DeviceHealthSnapshot healthSnapshot() override
{
return health_;
}
private:
device::DeviceKind kind_;
std::string type_name_;
device::DeviceHealthSnapshot health_;
};
std::uint64_t currentUnixTimeMs()
{
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch());
return static_cast<std::uint64_t>(elapsed.count());
}
const api::SystemDeviceInfo* findDevice(
const api::GetDeviceListCommand_Feedback& response,
const std::string& id)
{
for (const auto& device : response.device_list()) {
if (device.device_id() == id) {
return &device;
}
}
return nullptr;
}
class GrpcSystemServiceTest : public ::testing::Test {
protected:
void SetUp() override
{
device::DeviceManager::destroyInstance();
}
void TearDown() override
{
service_.reset();
owned_devices_.clear();
device::DeviceManager::destroyInstance();
}
api::GetDeviceListCommand_Feedback getDeviceList()
{
api::GetDeviceListCommand_Request request;
api::GetDeviceListCommand_Feedback response;
grpc::ServerContext context;
const auto status = service_->GetDeviceList(
&context, &request, &response);
EXPECT_TRUE(status.ok()) << status.error_message();
return response;
}
void registerDevice(device::DeviceManager& manager,
std::shared_ptr<SnapshotDevice> device)
{
manager.registerDevice(device);
owned_devices_.push_back(std::move(device));
}
std::unique_ptr<gRPCSystemServiceImpl> service_;
std::vector<std::shared_ptr<SnapshotDevice>> owned_devices_;
};
TEST_F(GrpcSystemServiceTest,
ReturnsOnlyEnabledDevicesAndPreservesEnabledErrors)
{
config::DeviceManagerConfig config;
config.set_name("system-service-test");
config.set_version("9.2");
config.set_description("GetDeviceList snapshot test");
auto* disabled = config.add_devices();
disabled->set_id("b_disabled_camera");
disabled->set_type(config::DeviceConfigEntry::DEVICE_TYPE_CAMERA);
disabled->set_enable(false);
// An enabled unsupported entry remains in the manager snapshot as ERROR.
// GetDeviceList must expose it rather than filtering by lifecycle state.
auto* enabled_error = config.add_devices();
enabled_error->set_id("c_enabled_error");
enabled_error->set_type(config::DeviceConfigEntry::DEVICE_TYPE_UNKNOWN);
enabled_error->set_enable(true);
auto& manager = device::DeviceManager::getInstance(config);
registerDevice(
manager,
std::make_shared<SnapshotDevice>(
"z_camera",
device::DeviceKind::Camera,
"VendorCamera",
device::DeviceHealthSnapshot{
device::DeviceHealthState::Healthy, {}}));
registerDevice(
manager,
std::make_shared<SnapshotDevice>(
"a_unknown",
device::DeviceKind::Unknown,
"MysteryDriver",
device::DeviceHealthSnapshot{
device::DeviceHealthState::Degraded,
"device health is degraded"}));
service_ = std::make_unique<gRPCSystemServiceImpl>();
const auto response = getDeviceList();
ASSERT_TRUE(response.header().success())
<< response.header().error_message();
EXPECT_TRUE(response.header().has_timestamp());
EXPECT_GT(response.header().timestamp().seconds(), 0);
EXPECT_EQ(response.manager_name(), "system-service-test");
EXPECT_EQ(response.manager_version(), "9.2");
EXPECT_EQ(response.manager_description(), "GetDeviceList snapshot test");
ASSERT_EQ(response.device_list_size(), 3);
// DeviceManager::snapshot() supplies stable device-id ordering, and the
// service must preserve it while filtering disabled entries.
EXPECT_EQ(response.device_list(0).device_id(), "a_unknown");
EXPECT_EQ(response.device_list(1).device_id(), "c_enabled_error");
EXPECT_EQ(response.device_list(2).device_id(), "z_camera");
EXPECT_EQ(findDevice(response, "b_disabled_camera"), nullptr);
const auto* unknown = findDevice(response, "a_unknown");
ASSERT_NE(unknown, nullptr);
EXPECT_TRUE(unknown->enabled());
EXPECT_EQ(unknown->type_name(), "MysteryDriver");
EXPECT_EQ(unknown->device_type(),
api::SYSTEM_DEVICE_TYPE_UNSPECIFIED);
EXPECT_NE(unknown->device_type(), api::SYSTEM_DEVICE_TYPE_AGV);
EXPECT_EQ(unknown->manager_state(),
api::SYSTEM_DEVICE_STATE_REGISTERED);
EXPECT_EQ(unknown->health(), api::SYSTEM_DEVICE_HEALTH_DEGRADED);
EXPECT_TRUE(unknown->has_error());
EXPECT_EQ(unknown->error_message(), "device health is degraded");
EXPECT_GT(unknown->status_updated_at_unix_ms(), 0U);
const auto* error = findDevice(response, "c_enabled_error");
ASSERT_NE(error, nullptr);
EXPECT_TRUE(error->enabled());
EXPECT_EQ(error->type_name(), "Unknown");
EXPECT_EQ(error->device_type(), api::SYSTEM_DEVICE_TYPE_UNSPECIFIED);
EXPECT_EQ(error->manager_state(), api::SYSTEM_DEVICE_STATE_ERROR);
EXPECT_EQ(error->health(), api::SYSTEM_DEVICE_HEALTH_UNSPECIFIED);
EXPECT_TRUE(error->has_error());
EXPECT_FALSE(error->error_message().empty());
EXPECT_GT(error->status_updated_at_unix_ms(), 0U);
const auto* camera = findDevice(response, "z_camera");
ASSERT_NE(camera, nullptr);
EXPECT_TRUE(camera->enabled());
EXPECT_EQ(camera->type_name(), "VendorCamera");
EXPECT_EQ(camera->device_type(), api::SYSTEM_DEVICE_TYPE_CAMERA);
EXPECT_EQ(camera->manager_state(),
api::SYSTEM_DEVICE_STATE_REGISTERED);
EXPECT_EQ(camera->health(), api::SYSTEM_DEVICE_HEALTH_HEALTHY);
EXPECT_FALSE(camera->has_error());
EXPECT_TRUE(camera->error_message().empty());
EXPECT_GT(camera->status_updated_at_unix_ms(), 0U);
}
TEST_F(GrpcSystemServiceTest, EmptyListReturnsMetadataAndTimestamps)
{
config::DeviceManagerConfig config;
config.set_name("empty-manager");
config.set_version("1.2.3");
config.set_description("manager without devices");
device::DeviceManager::getInstance(config);
service_ = std::make_unique<gRPCSystemServiceImpl>();
const auto before_ms = currentUnixTimeMs();
const auto response = getDeviceList();
const auto after_ms = currentUnixTimeMs();
ASSERT_TRUE(response.header().success())
<< response.header().error_message();
EXPECT_TRUE(response.header().has_timestamp());
EXPECT_GT(response.header().timestamp().seconds(), 0);
EXPECT_EQ(response.device_list_size(), 0);
EXPECT_EQ(response.manager_name(), "empty-manager");
EXPECT_EQ(response.manager_version(), "1.2.3");
EXPECT_EQ(response.manager_description(), "manager without devices");
EXPECT_GE(response.sampled_at_unix_ms(), before_ms);
EXPECT_LE(response.sampled_at_unix_ms(), after_ms);
}
TEST_F(GrpcSystemServiceTest, MapsEveryKnownDeviceKind)
{
struct ExpectedMapping {
const char* id;
device::DeviceKind source;
api::SystemDeviceType destination;
};
constexpr std::array<ExpectedMapping, 15> mappings{{
{"01_agv", device::DeviceKind::AGV,
api::SYSTEM_DEVICE_TYPE_AGV},
{"02_arm", device::DeviceKind::Arm,
api::SYSTEM_DEVICE_TYPE_ARM},
{"03_battery", device::DeviceKind::Battery,
api::SYSTEM_DEVICE_TYPE_BATTERY},
{"04_bio_head", device::DeviceKind::BioHead,
api::SYSTEM_DEVICE_TYPE_BIO_HEAD},
{"05_camera", device::DeviceKind::Camera,
api::SYSTEM_DEVICE_TYPE_CAMERA},
{"06_can_bus", device::DeviceKind::CanBus,
api::SYSTEM_DEVICE_TYPE_CAN_BUS},
{"07_dex_hand", device::DeviceKind::DexHand,
api::SYSTEM_DEVICE_TYPE_DEX_HAND},
{"08_gripper", device::DeviceKind::Gripper,
api::SYSTEM_DEVICE_TYPE_GRIPPER},
{"09_microphone", device::DeviceKind::Microphone,
api::SYSTEM_DEVICE_TYPE_MICROPHONE},
{"10_motor", device::DeviceKind::Motor,
api::SYSTEM_DEVICE_TYPE_MOTOR},
{"11_motor_system", device::DeviceKind::MotorSystem,
api::SYSTEM_DEVICE_TYPE_MOTOR_SYSTEM},
{"12_mujoco_viewer", device::DeviceKind::MujocoViewer,
api::SYSTEM_DEVICE_TYPE_MUJOCO_VIEWER},
{"13_mujoco_world", device::DeviceKind::MujocoWorld,
api::SYSTEM_DEVICE_TYPE_MUJOCO_WORLD},
{"14_robot", device::DeviceKind::Robot,
api::SYSTEM_DEVICE_TYPE_ROBOT},
{"15_speaker", device::DeviceKind::Speaker,
api::SYSTEM_DEVICE_TYPE_SPEAKER},
}};
config::DeviceManagerConfig config;
auto& manager = device::DeviceManager::getInstance(config);
for (const auto& mapping : mappings) {
registerDevice(
manager,
std::make_shared<SnapshotDevice>(
mapping.id, mapping.source, "MappedBackend"));
}
service_ = std::make_unique<gRPCSystemServiceImpl>();
const auto response = getDeviceList();
ASSERT_TRUE(response.header().success())
<< response.header().error_message();
ASSERT_EQ(response.device_list_size(),
static_cast<int>(mappings.size()));
for (std::size_t index = 0; index < mappings.size(); ++index) {
SCOPED_TRACE(mappings[index].id);
const auto& actual = response.device_list(
static_cast<int>(index));
EXPECT_EQ(actual.device_id(), mappings[index].id);
EXPECT_EQ(actual.device_type(), mappings[index].destination);
EXPECT_NE(actual.device_type(),
api::SYSTEM_DEVICE_TYPE_UNSPECIFIED);
}
}
} // namespace
} // namespace cmvr::service

View File

@ -20,4 +20,7 @@ service ArmService {
rpc calibrateZeroQ(CalibrateZeroQ.Request) returns (CalibrateZeroQ.Response); rpc calibrateZeroQ(CalibrateZeroQ.Request) returns (CalibrateZeroQ.Response);
rpc getPoseMatrix(GetPoseMatrix.Request) returns (GetPoseMatrix.Response); rpc getPoseMatrix(GetPoseMatrix.Request) returns (GetPoseMatrix.Response);
rpc computeForwardKinematics(ComputeForwardKinematics.Request) returns (ComputeForwardKinematics.Response); rpc computeForwardKinematics(ComputeForwardKinematics.Request) returns (ComputeForwardKinematics.Response);
// Vendor-specific arm extension currently used for AUBO cabinet IO.
rpc ExecuteJsonCommand(JsonDeviceCommand.Request) returns (JsonDeviceCommand.Feedback);
} }

View File

@ -21,6 +21,77 @@ message DeviceList {
DeviceType device_type = 2; DeviceType device_type = 2;
} }
// Stable device categories used by GetDeviceList. This intentionally does not
// reuse the legacy DeviceType enum above: its zero value is AGV and it does not
// cover all DeviceManager categories.
enum SystemDeviceType {
SYSTEM_DEVICE_TYPE_UNSPECIFIED = 0;
SYSTEM_DEVICE_TYPE_AGV = 1;
SYSTEM_DEVICE_TYPE_ARM = 2;
SYSTEM_DEVICE_TYPE_BATTERY = 3;
SYSTEM_DEVICE_TYPE_BIO_HEAD = 4;
SYSTEM_DEVICE_TYPE_CAMERA = 5;
SYSTEM_DEVICE_TYPE_CAN_BUS = 6;
SYSTEM_DEVICE_TYPE_DEX_HAND = 7;
SYSTEM_DEVICE_TYPE_GRIPPER = 8;
SYSTEM_DEVICE_TYPE_MICROPHONE = 9;
SYSTEM_DEVICE_TYPE_MOTOR = 10;
SYSTEM_DEVICE_TYPE_MOTOR_SYSTEM = 11;
SYSTEM_DEVICE_TYPE_MUJOCO_VIEWER = 12;
SYSTEM_DEVICE_TYPE_MUJOCO_WORLD = 13;
SYSTEM_DEVICE_TYPE_ROBOT = 14;
SYSTEM_DEVICE_TYPE_SPEAKER = 15;
}
enum SystemDeviceState {
SYSTEM_DEVICE_STATE_UNSPECIFIED = 0;
SYSTEM_DEVICE_STATE_DISABLED = 1;
SYSTEM_DEVICE_STATE_INITIALIZING = 2;
SYSTEM_DEVICE_STATE_REGISTERED = 3;
SYSTEM_DEVICE_STATE_READY = 4;
SYSTEM_DEVICE_STATE_RUNNING = 5;
SYSTEM_DEVICE_STATE_STOPPED = 6;
SYSTEM_DEVICE_STATE_ERROR = 7;
}
enum SystemDeviceHealth {
SYSTEM_DEVICE_HEALTH_UNSPECIFIED = 0;
SYSTEM_DEVICE_HEALTH_HEALTHY = 1;
SYSTEM_DEVICE_HEALTH_DEGRADED = 2;
SYSTEM_DEVICE_HEALTH_FAULT = 3;
}
message SystemDeviceInfo {
string device_id = 1;
SystemDeviceType device_type = 2;
// Concrete backend name for display and diagnostics only. Consumers must
// use device_type, rather than this free-form string, for decisions.
string type_name = 3;
// GetDeviceList currently publishes only enabled entries. Keep this field
// explicit so each row remains self-describing and future-compatible.
bool enabled = 4;
SystemDeviceState manager_state = 5;
SystemDeviceHealth health = 6;
bool has_error = 7;
string error_message = 8;
uint64 status_updated_at_unix_ms = 9;
}
message GetDeviceListCommand {
message Request {}
message Feedback {
CommandHeader.Feedback header = 1;
string manager_name = 2;
string manager_version = 3;
string manager_description = 4;
repeated SystemDeviceInfo device_list = 5;
uint64 sampled_at_unix_ms = 6;
}
}
message GetSystemInfoCommand { message GetSystemInfoCommand {
message Request {} message Request {}
@ -69,4 +140,4 @@ message StopAllCommand {
message Feedback { message Feedback {
CommandHeader.Feedback header = 1; CommandHeader.Feedback header = 1;
} }
} }

View File

@ -1,6 +1,5 @@
syntax = "proto3"; syntax = "proto3";
import "cmvr/api/common.proto";
import "cmvr/api/system_command.proto"; import "cmvr/api/system_command.proto";
package cmvr.api; package cmvr.api;
@ -9,9 +8,9 @@ package cmvr.api;
service SystemService { service SystemService {
rpc GetSystemInfo(GetSystemInfoCommand.Request) returns (GetSystemInfoCommand.Feedback) {} rpc GetSystemInfo(GetSystemInfoCommand.Request) returns (GetSystemInfoCommand.Feedback) {}
rpc GetSystemStatus(GetSystemStatusCommand.Request) returns (GetSystemStatusCommand.Feedback) {} rpc GetSystemStatus(GetSystemStatusCommand.Request) returns (GetSystemStatusCommand.Feedback) {}
rpc GetDeviceList(GetDeviceListCommand.Request) returns (GetDeviceListCommand.Feedback) {}
rpc UpdateParams(UpdateParamsCommand.Request) returns (UpdateParamsCommand.Feedback) {} rpc UpdateParams(UpdateParamsCommand.Request) returns (UpdateParamsCommand.Feedback) {}
rpc ExecuteJsonCommand(JsonDeviceCommand.Request) returns (JsonDeviceCommand.Feedback) {}
rpc StopAll(StopAllCommand.Request) returns (StopAllCommand.Feedback) {} rpc StopAll(StopAllCommand.Request) returns (StopAllCommand.Feedback) {}
} }