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_property(TARGET gRPC::grpc_cpp_plugin
|
||||
PROPERTY IMPORTED_LOCATION "${CMAKE_INSTALL_PREFIX}/bin/grpc_cpp_plugin"
|
||||
set_target_properties(gRPC::grpc_cpp_plugin PROPERTIES
|
||||
IMPORTED_LOCATION "${CMAKE_INSTALL_PREFIX}/bin/grpc_cpp_plugin"
|
||||
IMPORTED_LOCATION_RELEASE "${CMAKE_INSTALL_PREFIX}/bin/grpc_cpp_plugin"
|
||||
)
|
||||
|
||||
# 1) 先做 OBJECT:只负责生成/编译 pb.cc
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
|
||||
`AuboArm` 是 AUBO SDK v0.27.1 的 `RobotArm` 后端。控制柜 Standard 数字 IO
|
||||
通过设备通用的 `executeJsonCommand` 接口访问,远程调用复用
|
||||
`cmvr.api.SystemService/ExecuteJsonCommand`,不经过 `ArmService` 或
|
||||
`MotorService`。
|
||||
`cmvr.api.ArmService/ExecuteJsonCommand`,不经过 `SystemService` 或
|
||||
`MotorService`。该 RPC 只路由到 `RobotArm`,不会把 JSON 命令转发给其他设备类型。
|
||||
旧的 `cmvr.api.SystemService/ExecuteJsonCommand` 不再注册,调用方必须更新服务路径;
|
||||
请求和响应消息结构保持不变。
|
||||
|
||||
返回 [Devices 模块指南](../../README.md) 或 [项目总览](../../../../README.md)。
|
||||
|
||||
@ -14,9 +16,9 @@
|
||||
- 设备配置:[`../../../config/devices/arm/aubo_arm.pb.txt`](../../../config/devices/arm/aubo_arm.pb.txt)
|
||||
- DeviceManager 配置:
|
||||
[`../../../config/manager/device_manager.pb.txt`](../../../config/manager/device_manager.pb.txt)
|
||||
- SystemService 实现:
|
||||
[`../../../service/grpc/src/grpc_system_service.cpp`](../../../service/grpc/src/grpc_system_service.cpp)
|
||||
- Proto:[`../../../../protos/cmvr/api/system_service.proto`](../../../../protos/cmvr/api/system_service.proto)
|
||||
- ArmService 实现:
|
||||
[`../../../service/grpc/src/grpc_arm_service.cpp`](../../../service/grpc/src/grpc_arm_service.cpp)
|
||||
- Proto:[`../../../../protos/cmvr/api/arm_service.proto`](../../../../protos/cmvr/api/arm_service.proto)
|
||||
|
||||
仓库配置使用 SDK RPC 端口 `30004`。现场部署必须填写真实控制器地址和凭据,
|
||||
不要把生产密码提交到默认配置。
|
||||
@ -68,7 +70,7 @@ grpcurl -plaintext \
|
||||
"requestJson":"{\"command\":\"cabinet_io\",\"operation\":\"get_di\",\"index\":0}"
|
||||
}' \
|
||||
127.0.0.1:50052 \
|
||||
cmvr.api.SystemService/ExecuteJsonCommand
|
||||
cmvr.api.ArmService/ExecuteJsonCommand
|
||||
```
|
||||
|
||||
设置 DO0 为高电平:
|
||||
@ -80,7 +82,7 @@ grpcurl -plaintext \
|
||||
"requestJson":"{\"command\":\"cabinet_io\",\"operation\":\"set_do\",\"index\":0,\"value\":true}"
|
||||
}' \
|
||||
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)
|
||||
|
||||
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
|
||||
grpc/tests/grpc_arm_teleop_service_test.cpp
|
||||
)
|
||||
|
||||
@ -21,6 +21,24 @@ gRPC 和 QUIC 的职责边界:
|
||||
- 实时音视频使用 QUIC DATAGRAM;
|
||||
- `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` 将 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)
|
||||
- [MotorService 与 CMVR PLC v1 完整协议](../../docs/motor_service_modbus_tcp.md)
|
||||
|
||||
AUBO 控制柜 IO 不经过 `MotorService` 或 `ArmService`,而是复用
|
||||
`SystemService/ExecuteJsonCommand`。厂商命令和安全约束见
|
||||
AUBO 控制柜 IO 不经过 `MotorService`,由
|
||||
`ArmService/ExecuteJsonCommand` 转发到目标 `RobotArm`。厂商命令和安全约束见
|
||||
[AUBO 控制柜 IO](../devices/arm/aubo_arm/README.md)。
|
||||
旧的 `SystemService/ExecuteJsonCommand` 已移除;相机 PTZ 应使用类型化的
|
||||
`CameraService/ControlPtz`。
|
||||
|
||||
## 新增 gRPC Service
|
||||
|
||||
|
||||
@ -50,6 +50,9 @@ public:
|
||||
grpc::Status computeForwardKinematics(grpc::ServerContext* context,
|
||||
const api::ComputeForwardKinematics_Request* request,
|
||||
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,
|
||||
const cmvr::api::CommandHeader_Request *request,
|
||||
cmvr::api::CommandHeader_Feedback *response) override;
|
||||
|
||||
@ -17,8 +17,8 @@ namespace cmvr::service
|
||||
~gRPCSystemServiceImpl() override = default;
|
||||
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 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 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;
|
||||
private:
|
||||
device::DeviceManager& dmgr_;
|
||||
|
||||
@ -510,6 +510,40 @@ grpc::Status gRPCArmServiceImpl::computeForwardKinematics(grpc::ServerContext*,
|
||||
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,
|
||||
const cmvr::api::CommandHeader_Request *request,
|
||||
cmvr::api::CommandHeader_Feedback *response)
|
||||
|
||||
@ -4,12 +4,107 @@
|
||||
|
||||
#include "../include/grpc_system_service.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
#include "common/base/logging/logger.h"
|
||||
|
||||
using namespace cmvr::device;
|
||||
using namespace cmvr::device;
|
||||
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()) {}
|
||||
|
||||
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)
|
||||
{
|
||||
response->mutable_header()->set_success(false);
|
||||
@ -91,37 +235,6 @@ grpc::Status gRPCSystemServiceImpl::UpdateParams(grpc::ServerContext* context, c
|
||||
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,
|
||||
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 getPoseMatrix(GetPoseMatrix.Request) returns (GetPoseMatrix.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;
|
||||
}
|
||||
|
||||
// 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 Request {}
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "cmvr/api/common.proto";
|
||||
import "cmvr/api/system_command.proto";
|
||||
|
||||
package cmvr.api;
|
||||
@ -9,9 +8,9 @@ package cmvr.api;
|
||||
service SystemService {
|
||||
rpc GetSystemInfo(GetSystemInfoCommand.Request) returns (GetSystemInfoCommand.Feedback) {}
|
||||
rpc GetSystemStatus(GetSystemStatusCommand.Request) returns (GetSystemStatusCommand.Feedback) {}
|
||||
rpc GetDeviceList(GetDeviceListCommand.Request) returns (GetDeviceListCommand.Feedback) {}
|
||||
|
||||
rpc UpdateParams(UpdateParamsCommand.Request) returns (UpdateParamsCommand.Feedback) {}
|
||||
rpc ExecuteJsonCommand(JsonDeviceCommand.Request) returns (JsonDeviceCommand.Feedback) {}
|
||||
|
||||
rpc StopAll(StopAllCommand.Request) returns (StopAllCommand.Feedback) {}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user