feat: add device inventory and move arm JSON RPC
This commit is contained in:
parent
490c141ca8
commit
b4ec07c851
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
使用源码默认配置时:
|
使用源码默认配置时:
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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_;
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
378
cmvr-es/service/grpc/tests/grpc_arm_service_test.cpp
Normal file
378
cmvr-es/service/grpc/tests/grpc_arm_service_test.cpp
Normal 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
|
||||||
291
cmvr-es/service/grpc/tests/grpc_system_service_test.cpp
Normal file
291
cmvr-es/service/grpc/tests/grpc_system_service_test.cpp
Normal 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
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user