add grpc action queue support
This commit is contained in:
parent
9d5a40c4f8
commit
d15d381b1d
@ -2,6 +2,7 @@
|
|||||||
#define CMVR_ES_ARM_TYPES_H
|
#define CMVR_ES_ARM_TYPES_H
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -157,6 +158,9 @@ struct MotionOptions {
|
|||||||
double jerk{5.0};
|
double jerk{5.0};
|
||||||
std::vector<double> joint_velocity_limits;
|
std::vector<double> joint_velocity_limits;
|
||||||
bool asynchronous{false};
|
bool asynchronous{false};
|
||||||
|
// Optional cooperative cancellation used by synchronous ActionQueue
|
||||||
|
// motion. Drivers must not retain this callback after the command returns.
|
||||||
|
std::function<bool()> cancellation_requested;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ServoOptions {
|
struct ServoOptions {
|
||||||
|
|||||||
@ -32,6 +32,7 @@ public:
|
|||||||
RobotMode getRobotMode() const override;
|
RobotMode getRobotMode() const override;
|
||||||
SafetyMode getSafetyMode() const override;
|
SafetyMode getSafetyMode() const override;
|
||||||
ControlMode getControlMode() const override { return ControlMode::Position; }
|
ControlMode getControlMode() const override { return ControlMode::Position; }
|
||||||
|
bool supportsActionQueueMotion() const noexcept override { return true; }
|
||||||
Result listBaseFrame(std::vector<std::string>& frame_names) const override;
|
Result listBaseFrame(std::vector<std::string>& frame_names) const override;
|
||||||
Result listTCPFrame(std::vector<std::string>& frame_names) const override;
|
Result listTCPFrame(std::vector<std::string>& frame_names) const override;
|
||||||
|
|
||||||
|
|||||||
@ -49,11 +49,40 @@ constexpr auto kAutoEnableReconnectInterval = std::chrono::milliseconds(500);
|
|||||||
constexpr auto kAutoEnableRetryInterval = std::chrono::seconds(1);
|
constexpr auto kAutoEnableRetryInterval = std::chrono::seconds(1);
|
||||||
constexpr auto kAutoEnableModeTimeout = std::chrono::seconds(10);
|
constexpr auto kAutoEnableModeTimeout = std::chrono::seconds(10);
|
||||||
|
|
||||||
int waitArrival(const arcs::aubo_sdk::RobotInterfacePtr& robot_interface)
|
int waitArrival(
|
||||||
|
const arcs::aubo_sdk::RobotInterfacePtr& robot_interface,
|
||||||
|
const std::function<bool()>& cancellation_requested = {})
|
||||||
{
|
{
|
||||||
|
const auto deadline =
|
||||||
|
std::chrono::steady_clock::now() + std::chrono::seconds(60);
|
||||||
|
const auto canceled = [&]() {
|
||||||
|
return cancellation_requested && cancellation_requested();
|
||||||
|
};
|
||||||
|
const auto hardwareEmergencyStopActive = [&]() {
|
||||||
|
const auto mode = robot_interface->getRobotState()->getSafetyModeType();
|
||||||
|
const int source = robot_interface->getRobotConfig()
|
||||||
|
->getRobotEmergencyStopSource();
|
||||||
|
using arcs::common_interface::SafetyModeType;
|
||||||
|
return mode == SafetyModeType::RobotEmergencyStop ||
|
||||||
|
mode == SafetyModeType::SystemEmergencyStop || source != 0;
|
||||||
|
};
|
||||||
|
const auto stopMotion = [&]() {
|
||||||
|
try {
|
||||||
|
(void)robot_interface->getMotionControl()->stopMove(true, true);
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
int retry_count = 0;
|
int retry_count = 0;
|
||||||
int exec_id = robot_interface->getMotionControl()->getExecId();
|
int exec_id = robot_interface->getMotionControl()->getExecId();
|
||||||
while (exec_id == -1 && retry_count++ < 5) {
|
while (exec_id == -1 && retry_count++ < 5) {
|
||||||
|
if (canceled()) {
|
||||||
|
stopMotion();
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
if (hardwareEmergencyStopActive()) {
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
exec_id = robot_interface->getMotionControl()->getExecId();
|
exec_id = robot_interface->getMotionControl()->getExecId();
|
||||||
}
|
}
|
||||||
@ -61,6 +90,17 @@ int waitArrival(const arcs::aubo_sdk::RobotInterfacePtr& robot_interface)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
while (robot_interface->getMotionControl()->getExecId() != -1) {
|
while (robot_interface->getMotionControl()->getExecId() != -1) {
|
||||||
|
if (canceled()) {
|
||||||
|
stopMotion();
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
if (hardwareEmergencyStopActive()) {
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
if (std::chrono::steady_clock::now() >= deadline) {
|
||||||
|
stopMotion();
|
||||||
|
return -4;
|
||||||
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -513,6 +553,10 @@ Result AuboArm::setSpeedScaling(const double scaling)
|
|||||||
|
|
||||||
Result AuboArm::moveJ(const JointPositionCommand& target, const MotionOptions& options)
|
Result AuboArm::moveJ(const JointPositionCommand& target, const MotionOptions& options)
|
||||||
{
|
{
|
||||||
|
if (options.cancellation_requested && options.cancellation_requested()) {
|
||||||
|
return Result::failure(ArmErrorCode::CommandRejected,
|
||||||
|
"[AuboArm] moveJ canceled before dispatch");
|
||||||
|
}
|
||||||
std::string error;
|
std::string error;
|
||||||
if (!validDof_(target.position.size(), error)) {
|
if (!validDof_(target.position.size(), error)) {
|
||||||
return Result::failure(ArmErrorCode::InvalidDof, error);
|
return Result::failure(ArmErrorCode::InvalidDof, error);
|
||||||
@ -548,7 +592,10 @@ Result AuboArm::moveJ(const JointPositionCommand& target, const MotionOptions& o
|
|||||||
ret,
|
ret,
|
||||||
arcs::common_interface::AUBO_OK,
|
arcs::common_interface::AUBO_OK,
|
||||||
arcs::common_interface::AUBO_REQUEST_IGNORE,
|
arcs::common_interface::AUBO_REQUEST_IGNORE,
|
||||||
[&robot_interface]() { return waitArrival(robot_interface); });
|
[&robot_interface, &options]() {
|
||||||
|
return waitArrival(
|
||||||
|
robot_interface, options.cancellation_requested);
|
||||||
|
});
|
||||||
if (outcome == aubo_internal::MotionCommandOutcome::CompletedWithoutMotion
|
if (outcome == aubo_internal::MotionCommandOutcome::CompletedWithoutMotion
|
||||||
|| outcome == aubo_internal::MotionCommandOutcome::CompletedAfterMotion) {
|
|| outcome == aubo_internal::MotionCommandOutcome::CompletedAfterMotion) {
|
||||||
return Result::success();
|
return Result::success();
|
||||||
@ -596,6 +643,10 @@ Result AuboArm::moveL(const CartesianPose& target,
|
|||||||
const std::string& base_frame,
|
const std::string& base_frame,
|
||||||
const std::string& tcp_frame)
|
const std::string& tcp_frame)
|
||||||
{
|
{
|
||||||
|
if (options.cancellation_requested && options.cancellation_requested()) {
|
||||||
|
return Result::failure(ArmErrorCode::CommandRejected,
|
||||||
|
"[AuboArm] moveL canceled before dispatch");
|
||||||
|
}
|
||||||
const auto ready = ensureMotionReady_("moveL");
|
const auto ready = ensureMotionReady_("moveL");
|
||||||
if (!ready.ok()) {
|
if (!ready.ok()) {
|
||||||
return ready;
|
return ready;
|
||||||
@ -691,7 +742,10 @@ Result AuboArm::moveL(const CartesianPose& target,
|
|||||||
ret,
|
ret,
|
||||||
arcs::common_interface::AUBO_OK,
|
arcs::common_interface::AUBO_OK,
|
||||||
arcs::common_interface::AUBO_REQUEST_IGNORE,
|
arcs::common_interface::AUBO_REQUEST_IGNORE,
|
||||||
[&robot_interface]() { return waitArrival(robot_interface); });
|
[&robot_interface, &options]() {
|
||||||
|
return waitArrival(
|
||||||
|
robot_interface, options.cancellation_requested);
|
||||||
|
});
|
||||||
if (outcome == aubo_internal::MotionCommandOutcome::CompletedWithoutMotion
|
if (outcome == aubo_internal::MotionCommandOutcome::CompletedWithoutMotion
|
||||||
|| outcome == aubo_internal::MotionCommandOutcome::CompletedAfterMotion) {
|
|| outcome == aubo_internal::MotionCommandOutcome::CompletedAfterMotion) {
|
||||||
return Result::success();
|
return Result::success();
|
||||||
|
|||||||
@ -338,6 +338,10 @@ Result HuayanRobot::listTCPFrame(
|
|||||||
|
|
||||||
Result HuayanRobot::moveJ(const JointPositionCommand& target, const MotionOptions& options)
|
Result HuayanRobot::moveJ(const JointPositionCommand& target, const MotionOptions& options)
|
||||||
{
|
{
|
||||||
|
if (options.cancellation_requested && options.cancellation_requested()) {
|
||||||
|
return Result::failure(ArmErrorCode::CommandRejected,
|
||||||
|
"[HuayanRobot] moveJ canceled before dispatch");
|
||||||
|
}
|
||||||
std::string error;
|
std::string error;
|
||||||
if (!validDof_(target.position.size(), error)) {
|
if (!validDof_(target.position.size(), error)) {
|
||||||
return Result::failure(ArmErrorCode::InvalidDof, error);
|
return Result::failure(ArmErrorCode::InvalidDof, error);
|
||||||
@ -369,7 +373,8 @@ Result HuayanRobot::moveJ(const JointPositionCommand& target, const MotionOption
|
|||||||
busy_.store(false);
|
busy_.store(false);
|
||||||
return hrResult_(ret, "moveJ");
|
return hrResult_(ret, "moveJ");
|
||||||
}
|
}
|
||||||
const auto wait_result = waitMotionDone_("moveJ", 60000);
|
const auto wait_result = waitMotionDone_(
|
||||||
|
"moveJ", 60000, options.cancellation_requested);
|
||||||
busy_.store(false);
|
busy_.store(false);
|
||||||
return wait_result;
|
return wait_result;
|
||||||
}
|
}
|
||||||
@ -422,6 +427,10 @@ Result HuayanRobot::moveL(const CartesianPose& target,
|
|||||||
const std::string& base_frame,
|
const std::string& base_frame,
|
||||||
const std::string& tcp_frame)
|
const std::string& tcp_frame)
|
||||||
{
|
{
|
||||||
|
if (options.cancellation_requested && options.cancellation_requested()) {
|
||||||
|
return Result::failure(ArmErrorCode::CommandRejected,
|
||||||
|
"[HuayanRobot] moveL canceled before dispatch");
|
||||||
|
}
|
||||||
const auto ready = ensureMotionReady_("moveL");
|
const auto ready = ensureMotionReady_("moveL");
|
||||||
if (!ready.ok()) {
|
if (!ready.ok()) {
|
||||||
return ready;
|
return ready;
|
||||||
@ -451,7 +460,8 @@ Result HuayanRobot::moveL(const CartesianPose& target,
|
|||||||
busy_.store(false);
|
busy_.store(false);
|
||||||
return hrResult_(ret, "moveL");
|
return hrResult_(ret, "moveL");
|
||||||
}
|
}
|
||||||
const auto wait_result = waitMotionDone_("moveL", 60000);
|
const auto wait_result = waitMotionDone_(
|
||||||
|
"moveL", 60000, options.cancellation_requested);
|
||||||
busy_.store(false);
|
busy_.store(false);
|
||||||
return wait_result;
|
return wait_result;
|
||||||
}
|
}
|
||||||
@ -1095,10 +1105,19 @@ std::string HuayanRobot::nextCommandId_() const
|
|||||||
return id_ + "_" + std::to_string(++command_seq_);
|
return id_ + "_" + std::to_string(++command_seq_);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HuayanRobot::waitMotionDone_(const std::string& context, const int timeout_ms) const
|
Result HuayanRobot::waitMotionDone_(
|
||||||
|
const std::string& context,
|
||||||
|
const int timeout_ms,
|
||||||
|
const std::function<bool()>& cancellation_requested) const
|
||||||
{
|
{
|
||||||
const auto start = std::chrono::steady_clock::now();
|
const auto start = std::chrono::steady_clock::now();
|
||||||
while (true) {
|
while (true) {
|
||||||
|
if (cancellation_requested && cancellation_requested()) {
|
||||||
|
(void)HRIF_GrpStop(box_id_, robot_id_);
|
||||||
|
return Result::failure(
|
||||||
|
ArmErrorCode::CommandRejected,
|
||||||
|
"[HuayanRobot] " + context + " canceled");
|
||||||
|
}
|
||||||
bool done = false;
|
bool done = false;
|
||||||
const int ret = HRIF_IsMotionDone(box_id_, robot_id_, done);
|
const int ret = HRIF_IsMotionDone(box_id_, robot_id_, done);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
@ -1146,7 +1165,7 @@ Result HuayanRobot::waitMotionDone_(const std::string& context, const int timeou
|
|||||||
"[HuayanRobot] " + context + " timeout");
|
"[HuayanRobot] " + context + " timeout");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -38,6 +39,7 @@ public:
|
|||||||
RobotMode getRobotMode() const override;
|
RobotMode getRobotMode() const override;
|
||||||
SafetyMode getSafetyMode() const override;
|
SafetyMode getSafetyMode() const override;
|
||||||
ControlMode getControlMode() const override { return servo_mode_.load() ? ControlMode::Servo : ControlMode::Position; }
|
ControlMode getControlMode() const override { return servo_mode_.load() ? ControlMode::Servo : ControlMode::Position; }
|
||||||
|
bool supportsActionQueueMotion() const noexcept override { return true; }
|
||||||
Result listBaseFrame(std::vector<std::string>& frame_names) const override;
|
Result listBaseFrame(std::vector<std::string>& frame_names) const override;
|
||||||
Result listTCPFrame(std::vector<std::string>& frame_names) const override;
|
Result listTCPFrame(std::vector<std::string>& frame_names) const override;
|
||||||
|
|
||||||
@ -130,7 +132,9 @@ private:
|
|||||||
CartesianVelocity readTcpVelocity_() const;
|
CartesianVelocity readTcpVelocity_() const;
|
||||||
std::vector<double> currentJointPositionDeg_() const;
|
std::vector<double> currentJointPositionDeg_() const;
|
||||||
std::string nextCommandId_() const;
|
std::string nextCommandId_() const;
|
||||||
Result waitMotionDone_(const std::string& context, int timeout_ms) const;
|
Result waitMotionDone_(const std::string& context,
|
||||||
|
int timeout_ms,
|
||||||
|
const std::function<bool()>& cancellation_requested = {}) const;
|
||||||
void startAutoEnableMonitor_();
|
void startAutoEnableMonitor_();
|
||||||
void stopAutoEnableMonitor_();
|
void stopAutoEnableMonitor_();
|
||||||
void autoEnableMonitorLoop_();
|
void autoEnableMonitorLoop_();
|
||||||
|
|||||||
@ -27,6 +27,9 @@ public:
|
|||||||
virtual RobotMode getRobotMode() const = 0;
|
virtual RobotMode getRobotMode() const = 0;
|
||||||
virtual SafetyMode getSafetyMode() const = 0;
|
virtual SafetyMode getSafetyMode() const = 0;
|
||||||
virtual ControlMode getControlMode() const = 0;
|
virtual ControlMode getControlMode() const = 0;
|
||||||
|
// ActionQueue requires synchronous motion and cooperative cancellation.
|
||||||
|
// Backends opt in only after both semantics are implemented.
|
||||||
|
virtual bool supportsActionQueueMotion() const noexcept { return false; }
|
||||||
virtual Result listBaseFrame(std::vector<std::string>& frame_names) const
|
virtual Result listBaseFrame(std::vector<std::string>& frame_names) const
|
||||||
{
|
{
|
||||||
frame_names.clear();
|
frame_names.clear();
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
|
|
||||||
add_library(service
|
add_library(service
|
||||||
|
grpc/action/src/action_queue.cpp
|
||||||
|
grpc/action/src/control_command_arbiter.cpp
|
||||||
grpc/src/grpc_camera_service.cpp
|
grpc/src/grpc_camera_service.cpp
|
||||||
grpc/src/grpc_system_service.cpp
|
grpc/src/grpc_system_service.cpp
|
||||||
grpc/src/grpc_speaker_service.cpp
|
grpc/src/grpc_speaker_service.cpp
|
||||||
@ -27,6 +29,25 @@ target_link_libraries(service PRIVATE
|
|||||||
add_library(cmvr_es::service ALIAS service)
|
add_library(cmvr_es::service ALIAS service)
|
||||||
install(TARGETS service LIBRARY DESTINATION lib)
|
install(TARGETS service LIBRARY DESTINATION lib)
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
enable_testing()
|
||||||
|
add_executable(action_queue_test
|
||||||
|
grpc/action/tests/action_queue_test.cpp
|
||||||
|
grpc/action/src/action_queue.cpp
|
||||||
|
grpc/action/src/control_command_arbiter.cpp)
|
||||||
|
target_link_libraries(action_queue_test PRIVATE
|
||||||
|
cmvr_es::proto
|
||||||
|
cmvr_es::logging
|
||||||
|
gtest
|
||||||
|
gtest_main
|
||||||
|
pthread)
|
||||||
|
set_target_properties(action_queue_test PROPERTIES
|
||||||
|
BUILD_RPATH "${CMAKE_BINARY_DIR};${CMAKE_SOURCE_DIR}/output/lib;${CMAKE_SOURCE_DIR}/dependency/${ARCH}/third_party/grpc/v1.76.0/lib")
|
||||||
|
add_test(NAME action_queue_test COMMAND action_queue_test)
|
||||||
|
set_tests_properties(action_queue_test PROPERTIES
|
||||||
|
ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}:${CMAKE_SOURCE_DIR}/output/lib:${CMAKE_SOURCE_DIR}/dependency/${ARCH}/third_party/grpc/v1.76.0/lib")
|
||||||
|
endif()
|
||||||
|
|
||||||
# --------------------------------------------------------
|
# --------------------------------------------------------
|
||||||
# Unit test
|
# Unit test
|
||||||
# --------------------------------------------------------
|
# --------------------------------------------------------
|
||||||
|
|||||||
60
cmvr-es/service/grpc/action/include/action_queue.h
Normal file
60
cmvr-es/service/grpc/action/include/action_queue.h
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#ifndef CMVR_ES_ACTION_QUEUE_H
|
||||||
|
#define CMVR_ES_ACTION_QUEUE_H
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "cmvr/api/system_command.pb.h"
|
||||||
|
|
||||||
|
namespace cmvr::device {
|
||||||
|
class RobotArm;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace cmvr::service {
|
||||||
|
|
||||||
|
// Executes one submitted sequence at a time. The RPC waits synchronously for
|
||||||
|
// the terminal result, while StopAll may cancel the active sequence from a
|
||||||
|
// different gRPC handler.
|
||||||
|
class ActionQueue final {
|
||||||
|
public:
|
||||||
|
using ArmResolver = std::function<std::shared_ptr<device::RobotArm>(
|
||||||
|
const std::string&)>;
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
Idle,
|
||||||
|
Validating,
|
||||||
|
Running,
|
||||||
|
Stopping,
|
||||||
|
Completed,
|
||||||
|
Failed,
|
||||||
|
Canceled,
|
||||||
|
TimedOut,
|
||||||
|
Rejected,
|
||||||
|
ShuttingDown,
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit ActionQueue(ArmResolver arm_resolver);
|
||||||
|
~ActionQueue();
|
||||||
|
|
||||||
|
ActionQueue(const ActionQueue&) = delete;
|
||||||
|
ActionQueue& operator=(const ActionQueue&) = delete;
|
||||||
|
|
||||||
|
void execute(const api::ActionQueueCommand_Request& request,
|
||||||
|
api::ActionQueueCommand_Feedback& feedback);
|
||||||
|
|
||||||
|
// Immediately removes every not-yet-dispatched step and requests a typed
|
||||||
|
// stop for the active arm. No ActionQueue mutex is held across that call.
|
||||||
|
void cancelAndClear();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
State state() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
std::unique_ptr<Impl> impl_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cmvr::service
|
||||||
|
|
||||||
|
#endif // CMVR_ES_ACTION_QUEUE_H
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
#ifndef CMVR_ES_CONTROL_COMMAND_ARBITER_H
|
||||||
|
#define CMVR_ES_CONTROL_COMMAND_ARBITER_H
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace cmvr::service {
|
||||||
|
|
||||||
|
// Process-local admission gate for gRPC control commands. Ordinary controls
|
||||||
|
// may overlap each other, but ActionQueue owns the control domain exclusively.
|
||||||
|
// Stop leases are preemptive: they never wait for the current owner and block
|
||||||
|
// all new admission until the stop operation returns.
|
||||||
|
class ControlCommandArbiter final {
|
||||||
|
public:
|
||||||
|
enum class LeaseKind {
|
||||||
|
None,
|
||||||
|
Control,
|
||||||
|
ActionQueue,
|
||||||
|
Stop,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Lease final {
|
||||||
|
public:
|
||||||
|
Lease() = default;
|
||||||
|
~Lease();
|
||||||
|
|
||||||
|
Lease(const Lease&) = delete;
|
||||||
|
Lease& operator=(const Lease&) = delete;
|
||||||
|
Lease(Lease&& other) noexcept;
|
||||||
|
Lease& operator=(Lease&& other) noexcept;
|
||||||
|
|
||||||
|
explicit operator bool() const noexcept { return owner_ != nullptr; }
|
||||||
|
void reset() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class ControlCommandArbiter;
|
||||||
|
Lease(ControlCommandArbiter* owner, LeaseKind kind)
|
||||||
|
: owner_(owner), kind_(kind)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlCommandArbiter* owner_{nullptr};
|
||||||
|
LeaseKind kind_{LeaseKind::None};
|
||||||
|
};
|
||||||
|
|
||||||
|
static ControlCommandArbiter& instance();
|
||||||
|
|
||||||
|
Lease tryAcquireControl();
|
||||||
|
Lease tryAcquireActionQueue();
|
||||||
|
Lease beginStop();
|
||||||
|
|
||||||
|
bool actionQueueActive() const;
|
||||||
|
std::size_t activeControlCount() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void release(LeaseKind kind) noexcept;
|
||||||
|
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
std::size_t active_controls_{0};
|
||||||
|
std::size_t active_stops_{0};
|
||||||
|
bool action_queue_active_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cmvr::service
|
||||||
|
|
||||||
|
#endif // CMVR_ES_CONTROL_COMMAND_ARBITER_H
|
||||||
45
cmvr-es/service/grpc/action/include/control_command_guard.h
Normal file
45
cmvr-es/service/grpc/action/include/control_command_guard.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#ifndef CMVR_ES_CONTROL_COMMAND_GUARD_H
|
||||||
|
#define CMVR_ES_CONTROL_COMMAND_GUARD_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <grpcpp/grpcpp.h>
|
||||||
|
|
||||||
|
#include "cmvr/api/common.pb.h"
|
||||||
|
#include "common/base/grpc_utils.h"
|
||||||
|
|
||||||
|
namespace cmvr::service {
|
||||||
|
|
||||||
|
inline const std::string& controlCommandBusyMessage()
|
||||||
|
{
|
||||||
|
static const std::string message =
|
||||||
|
"control command rejected while ActionQueue or StopAll is active";
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline grpc::Status rejectControlCommand(api::CommandHeader_Feedback* response)
|
||||||
|
{
|
||||||
|
response->set_success(false);
|
||||||
|
response->set_error_message(controlCommandBusyMessage());
|
||||||
|
setCurrentTimestamp(response->mutable_timestamp());
|
||||||
|
return grpc::Status(
|
||||||
|
grpc::StatusCode::FAILED_PRECONDITION,
|
||||||
|
controlCommandBusyMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Response>
|
||||||
|
grpc::Status rejectControlCommand(Response* response)
|
||||||
|
{
|
||||||
|
return rejectControlCommand(response->mutable_header());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline grpc::Status rejectControlCommand()
|
||||||
|
{
|
||||||
|
return grpc::Status(
|
||||||
|
grpc::StatusCode::FAILED_PRECONDITION,
|
||||||
|
controlCommandBusyMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cmvr::service
|
||||||
|
|
||||||
|
#endif // CMVR_ES_CONTROL_COMMAND_GUARD_H
|
||||||
575
cmvr-es/service/grpc/action/src/action_queue.cpp
Normal file
575
cmvr-es/service/grpc/action/src/action_queue.cpp
Normal file
@ -0,0 +1,575 @@
|
|||||||
|
#include "service/grpc/action/include/action_queue.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <google/protobuf/util/time_util.h>
|
||||||
|
|
||||||
|
#include "common/base/logging/logger.h"
|
||||||
|
#include "devices/arm/robot_arm.h"
|
||||||
|
#include "service/grpc/action/include/control_command_arbiter.h"
|
||||||
|
|
||||||
|
namespace cmvr::service {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr std::size_t kMaximumStepCount = 100;
|
||||||
|
constexpr auto kDefaultTotalTimeout = std::chrono::minutes(5);
|
||||||
|
constexpr auto kMaximumTimeout = std::chrono::minutes(30);
|
||||||
|
constexpr auto kMaximumTimeoutMs =
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(kMaximumTimeout)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
bool finiteNonNegative(const double value)
|
||||||
|
{
|
||||||
|
return std::isfinite(value) && value >= 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
device::JointPositionCommand toJointPosition(
|
||||||
|
const api::JointPositionCommand& source)
|
||||||
|
{
|
||||||
|
device::JointPositionCommand target;
|
||||||
|
target.position.assign(source.position().begin(), source.position().end());
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
device::MotionOptions toMotionOptions(const api::MotionOptions& source)
|
||||||
|
{
|
||||||
|
device::MotionOptions options;
|
||||||
|
options.velocity = source.velocity();
|
||||||
|
options.acceleration = source.acceleration();
|
||||||
|
options.blend_radius = source.blend_radius();
|
||||||
|
options.jerk = source.jerk() > 0.0 ? source.jerk() : 5.0;
|
||||||
|
options.joint_velocity_limits.assign(
|
||||||
|
source.joint_velocity_limits().begin(),
|
||||||
|
source.joint_velocity_limits().end());
|
||||||
|
options.asynchronous = source.asynchronous();
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
device::CartesianPose toCartesianPose(const api::CartesianPose& source)
|
||||||
|
{
|
||||||
|
return {source.x(), source.y(), source.z(),
|
||||||
|
source.rx(), source.ry(), source.rz()};
|
||||||
|
}
|
||||||
|
|
||||||
|
device::FrameType toFrameType(const api::ArmFrameType frame)
|
||||||
|
{
|
||||||
|
switch (frame) {
|
||||||
|
case api::ARM_FRAME_TOOL:
|
||||||
|
return device::FrameType::Tool;
|
||||||
|
case api::ARM_FRAME_WORLD:
|
||||||
|
return device::FrameType::World;
|
||||||
|
case api::ARM_FRAME_USER:
|
||||||
|
return device::FrameType::User;
|
||||||
|
case api::ARM_FRAME_BASE:
|
||||||
|
default:
|
||||||
|
return device::FrameType::Base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validMotionOptions(const api::MotionOptions& options, std::string& error)
|
||||||
|
{
|
||||||
|
if (options.asynchronous()) {
|
||||||
|
error = "ActionQueue requires synchronous RobotArm motion";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!finiteNonNegative(options.velocity()) ||
|
||||||
|
!finiteNonNegative(options.acceleration()) ||
|
||||||
|
!finiteNonNegative(options.blend_radius()) ||
|
||||||
|
!finiteNonNegative(options.jerk())) {
|
||||||
|
error = "motion options must be finite and non-negative";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const double limit : options.joint_velocity_limits()) {
|
||||||
|
if (!finiteNonNegative(limit)) {
|
||||||
|
error = "joint velocity limits must be finite and non-negative";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishFeedback(api::ActionQueueCommand_Feedback& feedback,
|
||||||
|
const api::ActionResultCode result,
|
||||||
|
const std::uint32_t completed_steps,
|
||||||
|
const std::string& error,
|
||||||
|
const int failed_step_index = -1)
|
||||||
|
{
|
||||||
|
feedback.Clear();
|
||||||
|
feedback.set_result(result);
|
||||||
|
feedback.set_completed_steps(completed_steps);
|
||||||
|
if (failed_step_index >= 0) {
|
||||||
|
feedback.set_failed_step_index(
|
||||||
|
static_cast<std::uint32_t>(failed_step_index));
|
||||||
|
}
|
||||||
|
auto* header = feedback.mutable_header();
|
||||||
|
header->set_success(result == api::ACTION_RESULT_CODE_COMPLETED);
|
||||||
|
header->set_error_message(error);
|
||||||
|
*header->mutable_timestamp() =
|
||||||
|
google::protobuf::util::TimeUtil::GetCurrentTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct ActionQueue::Impl {
|
||||||
|
struct PreparedStep {
|
||||||
|
api::ActionStep source;
|
||||||
|
std::shared_ptr<device::RobotArm> arm;
|
||||||
|
std::size_t source_index{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CommandHandler {
|
||||||
|
std::function<bool(const api::ActionStep&,
|
||||||
|
std::size_t,
|
||||||
|
PreparedStep&,
|
||||||
|
std::string&)> prepare;
|
||||||
|
std::function<device::Result(
|
||||||
|
const PreparedStep&,
|
||||||
|
const std::function<bool()>&)> execute;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit Impl(ActionQueue::ArmResolver resolver)
|
||||||
|
: arm_resolver(std::move(resolver))
|
||||||
|
{
|
||||||
|
registerCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerCommands()
|
||||||
|
{
|
||||||
|
handlers.emplace(
|
||||||
|
api::ActionStep::kArmMoveJ,
|
||||||
|
CommandHandler{
|
||||||
|
[this](const api::ActionStep& step,
|
||||||
|
const std::size_t index,
|
||||||
|
PreparedStep& prepared,
|
||||||
|
std::string& error) {
|
||||||
|
const auto& command = step.arm_move_j();
|
||||||
|
return prepareArmStep(
|
||||||
|
step, command.header().device_id(), index,
|
||||||
|
command.options(), prepared, error,
|
||||||
|
[&command, &error](const device::RobotArm& arm) {
|
||||||
|
if (command.target().position_size() !=
|
||||||
|
static_cast<int>(arm.getDof())) {
|
||||||
|
error = "MoveJ target size does not match arm DOF";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const double value : command.target().position()) {
|
||||||
|
if (!std::isfinite(value)) {
|
||||||
|
error = "MoveJ target must contain finite values";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[](const PreparedStep& prepared,
|
||||||
|
const std::function<bool()>& canceled) {
|
||||||
|
const auto& command = prepared.source.arm_move_j();
|
||||||
|
auto options = toMotionOptions(command.options());
|
||||||
|
options.cancellation_requested = canceled;
|
||||||
|
return prepared.arm->moveJ(
|
||||||
|
toJointPosition(command.target()), options);
|
||||||
|
}});
|
||||||
|
|
||||||
|
handlers.emplace(
|
||||||
|
api::ActionStep::kArmMoveL,
|
||||||
|
CommandHandler{
|
||||||
|
[this](const api::ActionStep& step,
|
||||||
|
const std::size_t index,
|
||||||
|
PreparedStep& prepared,
|
||||||
|
std::string& error) {
|
||||||
|
const auto& command = step.arm_move_l();
|
||||||
|
return prepareArmStep(
|
||||||
|
step, command.header().device_id(), index,
|
||||||
|
command.options(), prepared, error,
|
||||||
|
[&command, &error](const device::RobotArm&) {
|
||||||
|
const auto& pose = command.target();
|
||||||
|
if (!std::isfinite(pose.x()) ||
|
||||||
|
!std::isfinite(pose.y()) ||
|
||||||
|
!std::isfinite(pose.z()) ||
|
||||||
|
!std::isfinite(pose.rx()) ||
|
||||||
|
!std::isfinite(pose.ry()) ||
|
||||||
|
!std::isfinite(pose.rz())) {
|
||||||
|
error = "MoveL target must contain finite values";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[](const PreparedStep& prepared,
|
||||||
|
const std::function<bool()>& canceled) {
|
||||||
|
const auto& command = prepared.source.arm_move_l();
|
||||||
|
auto options = toMotionOptions(command.options());
|
||||||
|
options.cancellation_requested = canceled;
|
||||||
|
const auto target = toCartesianPose(command.target());
|
||||||
|
const bool named_frame =
|
||||||
|
(command.has_base_frame() &&
|
||||||
|
!command.base_frame().empty()) ||
|
||||||
|
(command.has_tcp_frame() &&
|
||||||
|
!command.tcp_frame().empty());
|
||||||
|
if (named_frame) {
|
||||||
|
return prepared.arm->moveL(
|
||||||
|
target,
|
||||||
|
options,
|
||||||
|
command.has_base_frame()
|
||||||
|
? command.base_frame()
|
||||||
|
: std::string{},
|
||||||
|
command.has_tcp_frame()
|
||||||
|
? command.tcp_frame()
|
||||||
|
: std::string{});
|
||||||
|
}
|
||||||
|
return prepared.arm->moveL(
|
||||||
|
target, options, toFrameType(command.frame()));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ExtraValidator>
|
||||||
|
bool prepareArmStep(const api::ActionStep& step,
|
||||||
|
const std::string& device_id,
|
||||||
|
const std::size_t index,
|
||||||
|
const api::MotionOptions& options,
|
||||||
|
PreparedStep& prepared,
|
||||||
|
std::string& error,
|
||||||
|
ExtraValidator&& validate)
|
||||||
|
{
|
||||||
|
if (device_id.empty()) {
|
||||||
|
error = "RobotArm device_id is required";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto arm = arm_resolver(device_id);
|
||||||
|
if (!arm) {
|
||||||
|
error = "RobotArm device not found: " + device_id;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!arm->supportsActionQueueMotion()) {
|
||||||
|
error = "RobotArm backend does not support ActionQueue motion: " +
|
||||||
|
device_id;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!validMotionOptions(options, error) || !validate(*arm)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
prepared.source = step;
|
||||||
|
prepared.arm = std::move(arm);
|
||||||
|
prepared.source_index = index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool prepare(const api::ActionQueueCommand_Request& request,
|
||||||
|
std::vector<PreparedStep>& prepared,
|
||||||
|
std::string& error,
|
||||||
|
int& failed_index)
|
||||||
|
{
|
||||||
|
if (request.steps_size() == 0) {
|
||||||
|
error = "ActionQueue requires at least one step";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (request.steps_size() > static_cast<int>(kMaximumStepCount)) {
|
||||||
|
error = "ActionQueue step count exceeds the server limit";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (request.total_timeout_ms() >
|
||||||
|
static_cast<std::uint64_t>(kMaximumTimeoutMs)) {
|
||||||
|
error = "ActionQueue total timeout exceeds the server limit";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepared.reserve(static_cast<std::size_t>(request.steps_size()));
|
||||||
|
for (int index = 0; index < request.steps_size(); ++index) {
|
||||||
|
const auto& step = request.steps(index);
|
||||||
|
if (step.timeout_ms() >
|
||||||
|
static_cast<std::uint64_t>(kMaximumTimeoutMs)) {
|
||||||
|
error = "step timeout exceeds the server limit";
|
||||||
|
failed_index = index;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto handler = handlers.find(step.command_case());
|
||||||
|
if (handler == handlers.end()) {
|
||||||
|
error = "ActionQueue command type is not supported";
|
||||||
|
failed_index = index;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PreparedStep item;
|
||||||
|
if (!handler->second.prepare(
|
||||||
|
step, static_cast<std::size_t>(index), item, error)) {
|
||||||
|
failed_index = index;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
prepared.push_back(std::move(item));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setState(const State next)
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
state = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionQueue::ArmResolver arm_resolver;
|
||||||
|
std::unordered_map<int, CommandHandler> handlers;
|
||||||
|
|
||||||
|
mutable std::mutex mutex;
|
||||||
|
State state{State::Idle};
|
||||||
|
std::deque<PreparedStep> pending;
|
||||||
|
std::shared_ptr<device::RobotArm> active_arm;
|
||||||
|
std::atomic<bool> stop_requested{false};
|
||||||
|
std::atomic<bool> shutting_down{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
ActionQueue::ActionQueue(ArmResolver arm_resolver)
|
||||||
|
: impl_(std::make_unique<Impl>(std::move(arm_resolver)))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionQueue::~ActionQueue()
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionQueue::execute(
|
||||||
|
const api::ActionQueueCommand_Request& request,
|
||||||
|
api::ActionQueueCommand_Feedback& feedback)
|
||||||
|
{
|
||||||
|
if (impl_->shutting_down.load()) {
|
||||||
|
finishFeedback(feedback, api::ACTION_RESULT_CODE_REJECTED, 0,
|
||||||
|
"ActionQueue is shutting down");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto action_lease =
|
||||||
|
ControlCommandArbiter::instance().tryAcquireActionQueue();
|
||||||
|
if (!action_lease) {
|
||||||
|
finishFeedback(
|
||||||
|
feedback, api::ACTION_RESULT_CODE_REJECTED, 0,
|
||||||
|
"ActionQueue cannot start while another control command is active");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(impl_->mutex);
|
||||||
|
impl_->stop_requested.store(false);
|
||||||
|
impl_->state = State::Validating;
|
||||||
|
}
|
||||||
|
std::vector<Impl::PreparedStep> prepared;
|
||||||
|
std::string error;
|
||||||
|
int failed_index = -1;
|
||||||
|
if (!impl_->prepare(request, prepared, error, failed_index)) {
|
||||||
|
if (impl_->stop_requested.load() || impl_->shutting_down.load()) {
|
||||||
|
impl_->setState(State::Canceled);
|
||||||
|
finishFeedback(feedback, api::ACTION_RESULT_CODE_CANCELED, 0,
|
||||||
|
"ActionQueue was stopped");
|
||||||
|
impl_->setState(impl_->shutting_down.load()
|
||||||
|
? State::ShuttingDown
|
||||||
|
: State::Idle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
impl_->setState(State::Rejected);
|
||||||
|
finishFeedback(feedback, api::ACTION_RESULT_CODE_REJECTED, 0,
|
||||||
|
error, failed_index);
|
||||||
|
impl_->setState(State::Idle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(impl_->mutex);
|
||||||
|
if (impl_->stop_requested.load() || impl_->shutting_down.load()) {
|
||||||
|
impl_->state = impl_->shutting_down.load()
|
||||||
|
? State::ShuttingDown
|
||||||
|
: State::Idle;
|
||||||
|
finishFeedback(feedback, api::ACTION_RESULT_CODE_CANCELED, 0,
|
||||||
|
"ActionQueue was stopped");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
impl_->pending.assign(
|
||||||
|
std::make_move_iterator(prepared.begin()),
|
||||||
|
std::make_move_iterator(prepared.end()));
|
||||||
|
impl_->active_arm.reset();
|
||||||
|
impl_->state = State::Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto total_timeout = request.total_timeout_ms() == 0
|
||||||
|
? kDefaultTotalTimeout
|
||||||
|
: std::chrono::milliseconds(request.total_timeout_ms());
|
||||||
|
const auto total_deadline = std::chrono::steady_clock::now() +
|
||||||
|
total_timeout;
|
||||||
|
std::uint32_t completed_steps = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
Impl::PreparedStep step;
|
||||||
|
{
|
||||||
|
std::lock_guard lock(impl_->mutex);
|
||||||
|
if (impl_->stop_requested.load() || impl_->shutting_down.load()) {
|
||||||
|
impl_->state = State::Canceled;
|
||||||
|
finishFeedback(feedback, api::ACTION_RESULT_CODE_CANCELED,
|
||||||
|
completed_steps, "ActionQueue was stopped");
|
||||||
|
impl_->active_arm.reset();
|
||||||
|
impl_->state = impl_->shutting_down.load()
|
||||||
|
? State::ShuttingDown
|
||||||
|
: State::Idle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (impl_->pending.empty()) {
|
||||||
|
impl_->state = State::Completed;
|
||||||
|
finishFeedback(feedback, api::ACTION_RESULT_CODE_COMPLETED,
|
||||||
|
completed_steps, {});
|
||||||
|
impl_->active_arm.reset();
|
||||||
|
impl_->state = State::Idle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (std::chrono::steady_clock::now() >= total_deadline) {
|
||||||
|
impl_->pending.clear();
|
||||||
|
impl_->state = State::TimedOut;
|
||||||
|
finishFeedback(feedback, api::ACTION_RESULT_CODE_TIMED_OUT,
|
||||||
|
completed_steps,
|
||||||
|
"ActionQueue total timeout expired");
|
||||||
|
impl_->state = State::Idle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
step = std::move(impl_->pending.front());
|
||||||
|
impl_->pending.pop_front();
|
||||||
|
impl_->active_arm = step.arm;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto step_deadline = total_deadline;
|
||||||
|
if (step.source.timeout_ms() != 0) {
|
||||||
|
step_deadline = std::min(
|
||||||
|
step_deadline,
|
||||||
|
std::chrono::steady_clock::now() +
|
||||||
|
std::chrono::milliseconds(step.source.timeout_ms()));
|
||||||
|
}
|
||||||
|
const auto canceled = [this, step_deadline]() {
|
||||||
|
return impl_->stop_requested.load() ||
|
||||||
|
impl_->shutting_down.load() ||
|
||||||
|
std::chrono::steady_clock::now() >= step_deadline;
|
||||||
|
};
|
||||||
|
|
||||||
|
device::Result result;
|
||||||
|
try {
|
||||||
|
const auto handler = impl_->handlers.find(
|
||||||
|
step.source.command_case());
|
||||||
|
result = handler->second.execute(step, canceled);
|
||||||
|
} catch (const std::exception& exception) {
|
||||||
|
result = device::Result::failure(
|
||||||
|
device::ArmErrorCode::CommandFailed, exception.what());
|
||||||
|
} catch (...) {
|
||||||
|
result = device::Result::failure(
|
||||||
|
device::ArmErrorCode::CommandFailed,
|
||||||
|
"ActionQueue command threw an unknown exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(impl_->mutex);
|
||||||
|
impl_->active_arm.reset();
|
||||||
|
if (impl_->stop_requested.load() || impl_->shutting_down.load()) {
|
||||||
|
impl_->pending.clear();
|
||||||
|
impl_->state = State::Canceled;
|
||||||
|
finishFeedback(
|
||||||
|
feedback, api::ACTION_RESULT_CODE_CANCELED,
|
||||||
|
completed_steps, "ActionQueue was stopped",
|
||||||
|
static_cast<int>(step.source_index));
|
||||||
|
impl_->state = impl_->shutting_down.load()
|
||||||
|
? State::ShuttingDown
|
||||||
|
: State::Idle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (std::chrono::steady_clock::now() >= step_deadline) {
|
||||||
|
impl_->pending.clear();
|
||||||
|
impl_->state = State::TimedOut;
|
||||||
|
finishFeedback(
|
||||||
|
feedback, api::ACTION_RESULT_CODE_TIMED_OUT,
|
||||||
|
completed_steps, "ActionQueue step timeout expired",
|
||||||
|
static_cast<int>(step.source_index));
|
||||||
|
impl_->state = State::Idle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!result.ok()) {
|
||||||
|
impl_->pending.clear();
|
||||||
|
impl_->state = State::Failed;
|
||||||
|
finishFeedback(
|
||||||
|
feedback, api::ACTION_RESULT_CODE_FAILED,
|
||||||
|
completed_steps, result.message,
|
||||||
|
static_cast<int>(step.source_index));
|
||||||
|
impl_->state = State::Idle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
++completed_steps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionQueue::cancelAndClear()
|
||||||
|
{
|
||||||
|
std::shared_ptr<device::RobotArm> active_arm;
|
||||||
|
{
|
||||||
|
std::lock_guard lock(impl_->mutex);
|
||||||
|
if (impl_->state == State::Idle ||
|
||||||
|
impl_->state == State::Rejected ||
|
||||||
|
impl_->state == State::Completed ||
|
||||||
|
impl_->state == State::Failed ||
|
||||||
|
impl_->state == State::Canceled ||
|
||||||
|
impl_->state == State::TimedOut) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
impl_->stop_requested.store(true);
|
||||||
|
impl_->pending.clear();
|
||||||
|
impl_->state = impl_->shutting_down.load()
|
||||||
|
? State::ShuttingDown
|
||||||
|
: State::Stopping;
|
||||||
|
active_arm = impl_->active_arm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never call a device SDK while holding the ActionQueue mutex. This keeps
|
||||||
|
// hardware emergency-stop and auto-enable monitor callbacks from forming a
|
||||||
|
// lock cycle with StopAll.
|
||||||
|
if (active_arm) {
|
||||||
|
try {
|
||||||
|
const auto result = active_arm->stopMotion();
|
||||||
|
if (!result.ok()) {
|
||||||
|
CMVR_LOG(WARNING)
|
||||||
|
<< "[ActionQueue] active arm stop was not confirmed: "
|
||||||
|
<< result.message;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
CMVR_LOG(WARNING)
|
||||||
|
<< "[ActionQueue] active arm stop threw: " << error.what();
|
||||||
|
} catch (...) {
|
||||||
|
CMVR_LOG(WARNING)
|
||||||
|
<< "[ActionQueue] active arm stop threw an unknown exception";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActionQueue::shutdown()
|
||||||
|
{
|
||||||
|
if (!impl_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
impl_->shutting_down.store(true);
|
||||||
|
cancelAndClear();
|
||||||
|
std::lock_guard lock(impl_->mutex);
|
||||||
|
if (!impl_->active_arm) {
|
||||||
|
impl_->state = State::ShuttingDown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionQueue::State ActionQueue::state() const
|
||||||
|
{
|
||||||
|
std::lock_guard lock(impl_->mutex);
|
||||||
|
return impl_->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cmvr::service
|
||||||
105
cmvr-es/service/grpc/action/src/control_command_arbiter.cpp
Normal file
105
cmvr-es/service/grpc/action/src/control_command_arbiter.cpp
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
#include "service/grpc/action/include/control_command_arbiter.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace cmvr::service {
|
||||||
|
|
||||||
|
ControlCommandArbiter::Lease::~Lease()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlCommandArbiter::Lease::Lease(Lease&& other) noexcept
|
||||||
|
: owner_(std::exchange(other.owner_, nullptr)),
|
||||||
|
kind_(std::exchange(other.kind_, LeaseKind::None))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlCommandArbiter::Lease& ControlCommandArbiter::Lease::operator=(
|
||||||
|
Lease&& other) noexcept
|
||||||
|
{
|
||||||
|
if (this != &other) {
|
||||||
|
reset();
|
||||||
|
owner_ = std::exchange(other.owner_, nullptr);
|
||||||
|
kind_ = std::exchange(other.kind_, LeaseKind::None);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlCommandArbiter::Lease::reset() noexcept
|
||||||
|
{
|
||||||
|
if (owner_) {
|
||||||
|
owner_->release(kind_);
|
||||||
|
owner_ = nullptr;
|
||||||
|
kind_ = LeaseKind::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlCommandArbiter& ControlCommandArbiter::instance()
|
||||||
|
{
|
||||||
|
static ControlCommandArbiter arbiter;
|
||||||
|
return arbiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlCommandArbiter::Lease ControlCommandArbiter::tryAcquireControl()
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
if (action_queue_active_ || active_stops_ != 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
++active_controls_;
|
||||||
|
return {this, LeaseKind::Control};
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlCommandArbiter::Lease ControlCommandArbiter::tryAcquireActionQueue()
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
if (action_queue_active_ || active_controls_ != 0 || active_stops_ != 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
action_queue_active_ = true;
|
||||||
|
return {this, LeaseKind::ActionQueue};
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlCommandArbiter::Lease ControlCommandArbiter::beginStop()
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
++active_stops_;
|
||||||
|
return {this, LeaseKind::Stop};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ControlCommandArbiter::actionQueueActive() const
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
return action_queue_active_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t ControlCommandArbiter::activeControlCount() const
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
return active_controls_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlCommandArbiter::release(const LeaseKind kind) noexcept
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
switch (kind) {
|
||||||
|
case LeaseKind::Control:
|
||||||
|
if (active_controls_ != 0) {
|
||||||
|
--active_controls_;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LeaseKind::ActionQueue:
|
||||||
|
action_queue_active_ = false;
|
||||||
|
break;
|
||||||
|
case LeaseKind::Stop:
|
||||||
|
if (active_stops_ != 0) {
|
||||||
|
--active_stops_;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LeaseKind::None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cmvr::service
|
||||||
330
cmvr-es/service/grpc/action/tests/action_queue_test.cpp
Normal file
330
cmvr-es/service/grpc/action/tests/action_queue_test.cpp
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <future>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "devices/arm/robot_arm.h"
|
||||||
|
#include "service/grpc/action/include/action_queue.h"
|
||||||
|
#include "service/grpc/action/include/control_command_arbiter.h"
|
||||||
|
|
||||||
|
namespace cmvr::service {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
class FakeActionQueueArm final : public device::RobotArm {
|
||||||
|
public:
|
||||||
|
explicit FakeActionQueueArm(std::string device_id)
|
||||||
|
{
|
||||||
|
id_ = std::move(device_id);
|
||||||
|
model_.dof = 6;
|
||||||
|
model_.joint_names = {"j1", "j2", "j3", "j4", "j5", "j6"};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string typeName() const override { return "FakeActionQueueArm"; }
|
||||||
|
bool stop() override { return stopMotion().ok(); }
|
||||||
|
device::RobotModel getRobotModel() const override { return model_; }
|
||||||
|
std::size_t getDof() const override { return model_.dof; }
|
||||||
|
device::ArmState getRobotState() const override { return {}; }
|
||||||
|
device::JointGroupState getJointState() const override { return {}; }
|
||||||
|
device::CartesianPose getTcpPose(device::FrameType) const override { return {}; }
|
||||||
|
device::RobotMode getRobotMode() const override { return device::RobotMode::Idle; }
|
||||||
|
device::SafetyMode getSafetyMode() const override {
|
||||||
|
return emergency_.load()
|
||||||
|
? device::SafetyMode::EmergencyStop
|
||||||
|
: device::SafetyMode::Normal;
|
||||||
|
}
|
||||||
|
device::ControlMode getControlMode() const override {
|
||||||
|
return device::ControlMode::Position;
|
||||||
|
}
|
||||||
|
bool supportsActionQueueMotion() const noexcept override { return true; }
|
||||||
|
|
||||||
|
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 {
|
||||||
|
emergency_.store(true);
|
||||||
|
return stopMotion();
|
||||||
|
}
|
||||||
|
device::Result protectiveStop() override { return emergencyStop(); }
|
||||||
|
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 emergency_.load(); }
|
||||||
|
bool isFault() const override { return false; }
|
||||||
|
|
||||||
|
device::Result moveJ(const device::JointPositionCommand&,
|
||||||
|
const device::MotionOptions& options) override
|
||||||
|
{
|
||||||
|
return runMotion("MoveJ", options);
|
||||||
|
}
|
||||||
|
device::Result speedJ(const device::JointVelocityCommand&, double, double) override {
|
||||||
|
return device::Result::success();
|
||||||
|
}
|
||||||
|
device::Result stopJ(double) override { return stopMotion(); }
|
||||||
|
device::Result moveL(const device::CartesianPose&,
|
||||||
|
const device::MotionOptions& options,
|
||||||
|
device::FrameType) override
|
||||||
|
{
|
||||||
|
return runMotion("MoveL", options);
|
||||||
|
}
|
||||||
|
device::Result moveL(const device::CartesianPose&,
|
||||||
|
const device::MotionOptions& options,
|
||||||
|
const std::string& base_frame,
|
||||||
|
const std::string& tcp_frame) override
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
base_frame_ = base_frame;
|
||||||
|
tcp_frame_ = tcp_frame;
|
||||||
|
}
|
||||||
|
return runMotion("MoveL", options);
|
||||||
|
}
|
||||||
|
device::Result speedL(const device::CartesianVelocity&, double, double,
|
||||||
|
device::FrameType) override {
|
||||||
|
return device::Result::success();
|
||||||
|
}
|
||||||
|
device::Result stopL(std::optional<double>) override { return stopMotion(); }
|
||||||
|
device::Result stopMotion() override
|
||||||
|
{
|
||||||
|
++stop_count_;
|
||||||
|
cv_.notify_all();
|
||||||
|
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) override { return device::Result::success(); }
|
||||||
|
device::Result servoSpeedJ(const device::JointVelocityCommand&) override { return device::Result::success(); }
|
||||||
|
device::Result servoSpeedL(const device::CartesianVelocity&, device::FrameType) 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) override { return {}; }
|
||||||
|
device::CartesianVelocity getSpeedLCommandTwistBase() const override { return {}; }
|
||||||
|
bool busy() const override { return busy_.load(); }
|
||||||
|
|
||||||
|
void blockMotion(bool value)
|
||||||
|
{
|
||||||
|
block_.store(value);
|
||||||
|
cv_.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool waitUntilStarted()
|
||||||
|
{
|
||||||
|
std::unique_lock lock(mutex_);
|
||||||
|
return cv_.wait_for(lock, 2s, [this] { return started_; });
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> calls() const
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
return calls_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string baseFrame() const
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
return base_frame_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string tcpFrame() const
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
return tcp_frame_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int stopCount() const { return stop_count_.load(); }
|
||||||
|
|
||||||
|
void triggerHardwareEstop()
|
||||||
|
{
|
||||||
|
emergency_.store(true);
|
||||||
|
cv_.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
device::Result runMotion(const std::string& name,
|
||||||
|
const device::MotionOptions& options)
|
||||||
|
{
|
||||||
|
busy_.store(true);
|
||||||
|
{
|
||||||
|
std::lock_guard lock(mutex_);
|
||||||
|
calls_.push_back(name);
|
||||||
|
started_ = true;
|
||||||
|
}
|
||||||
|
cv_.notify_all();
|
||||||
|
while (block_.load()) {
|
||||||
|
if (emergency_.load()) {
|
||||||
|
busy_.store(false);
|
||||||
|
return device::Result::failure(
|
||||||
|
device::ArmErrorCode::RobotInEmergencyStop,
|
||||||
|
"hardware emergency stop");
|
||||||
|
}
|
||||||
|
if (options.cancellation_requested &&
|
||||||
|
options.cancellation_requested()) {
|
||||||
|
busy_.store(false);
|
||||||
|
return device::Result::failure(
|
||||||
|
device::ArmErrorCode::CommandRejected, "canceled");
|
||||||
|
}
|
||||||
|
std::unique_lock lock(mutex_);
|
||||||
|
cv_.wait_for(lock, 10ms);
|
||||||
|
}
|
||||||
|
busy_.store(false);
|
||||||
|
return emergency_.load()
|
||||||
|
? device::Result::failure(
|
||||||
|
device::ArmErrorCode::RobotInEmergencyStop,
|
||||||
|
"hardware emergency stop")
|
||||||
|
: device::Result::success();
|
||||||
|
}
|
||||||
|
|
||||||
|
device::RobotModel model_;
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
std::condition_variable cv_;
|
||||||
|
std::vector<std::string> calls_;
|
||||||
|
std::string base_frame_;
|
||||||
|
std::string tcp_frame_;
|
||||||
|
bool started_{false};
|
||||||
|
std::atomic<bool> block_{false};
|
||||||
|
std::atomic<bool> busy_{false};
|
||||||
|
std::atomic<bool> emergency_{false};
|
||||||
|
std::atomic<int> stop_count_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
ActionQueue makeQueue(const std::shared_ptr<FakeActionQueueArm>& arm)
|
||||||
|
{
|
||||||
|
return ActionQueue([arm](const std::string& device_id) {
|
||||||
|
return device_id == arm->id()
|
||||||
|
? std::static_pointer_cast<device::RobotArm>(arm)
|
||||||
|
: std::shared_ptr<device::RobotArm>{};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
api::ActionQueueCommand_Request twoStepRequest(const std::string& device_id)
|
||||||
|
{
|
||||||
|
api::ActionQueueCommand_Request request;
|
||||||
|
auto* move_j = request.add_steps()->mutable_arm_move_j();
|
||||||
|
move_j->mutable_header()->set_device_id(device_id);
|
||||||
|
for (int index = 0; index < 6; ++index) {
|
||||||
|
move_j->mutable_target()->add_position(0.1 * index);
|
||||||
|
}
|
||||||
|
auto* move_l = request.add_steps()->mutable_arm_move_l();
|
||||||
|
move_l->mutable_header()->set_device_id(device_id);
|
||||||
|
move_l->set_base_frame("workpiece");
|
||||||
|
move_l->set_tcp_frame("gripper");
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ActionQueueTest, ExecutesMoveJAndNamedFrameMoveLInOrder)
|
||||||
|
{
|
||||||
|
auto arm = std::make_shared<FakeActionQueueArm>("action_arm_order");
|
||||||
|
auto queue = makeQueue(arm);
|
||||||
|
api::ActionQueueCommand_Feedback feedback;
|
||||||
|
|
||||||
|
queue.execute(twoStepRequest(arm->id()), feedback);
|
||||||
|
|
||||||
|
EXPECT_TRUE(feedback.header().success())
|
||||||
|
<< feedback.header().error_message();
|
||||||
|
EXPECT_EQ(feedback.result(), api::ACTION_RESULT_CODE_COMPLETED);
|
||||||
|
EXPECT_EQ(feedback.completed_steps(), 2U);
|
||||||
|
EXPECT_EQ(arm->calls(), (std::vector<std::string>{"MoveJ", "MoveL"}));
|
||||||
|
EXPECT_EQ(arm->baseFrame(), "workpiece");
|
||||||
|
EXPECT_EQ(arm->tcpFrame(), "gripper");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ActionQueueTest, StopClearsRemainingStepsAndReleasesControlDomain)
|
||||||
|
{
|
||||||
|
auto arm = std::make_shared<FakeActionQueueArm>("action_arm_stop");
|
||||||
|
arm->blockMotion(true);
|
||||||
|
auto queue = makeQueue(arm);
|
||||||
|
api::ActionQueueCommand_Feedback feedback;
|
||||||
|
|
||||||
|
std::thread executor([&] {
|
||||||
|
queue.execute(twoStepRequest(arm->id()), feedback);
|
||||||
|
});
|
||||||
|
ASSERT_TRUE(arm->waitUntilStarted());
|
||||||
|
EXPECT_FALSE(ControlCommandArbiter::instance().tryAcquireControl());
|
||||||
|
|
||||||
|
queue.cancelAndClear();
|
||||||
|
executor.join();
|
||||||
|
|
||||||
|
EXPECT_EQ(feedback.result(), api::ACTION_RESULT_CODE_CANCELED);
|
||||||
|
EXPECT_EQ(feedback.completed_steps(), 0U);
|
||||||
|
EXPECT_EQ(arm->calls(), (std::vector<std::string>{"MoveJ"}));
|
||||||
|
EXPECT_GE(arm->stopCount(), 1);
|
||||||
|
EXPECT_TRUE(ControlCommandArbiter::instance().tryAcquireControl());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ActionQueueTest, RejectsSubmissionWhileOrdinaryControlIsActive)
|
||||||
|
{
|
||||||
|
auto arm = std::make_shared<FakeActionQueueArm>("action_arm_gate");
|
||||||
|
auto queue = makeQueue(arm);
|
||||||
|
api::ActionQueueCommand_Feedback feedback;
|
||||||
|
auto control_lease =
|
||||||
|
ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
ASSERT_TRUE(control_lease);
|
||||||
|
|
||||||
|
queue.execute(twoStepRequest(arm->id()), feedback);
|
||||||
|
|
||||||
|
EXPECT_EQ(feedback.result(), api::ACTION_RESULT_CODE_REJECTED);
|
||||||
|
EXPECT_TRUE(arm->calls().empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ActionQueueTest, HardwareEmergencyStopTerminatesWithoutHoldingTheGate)
|
||||||
|
{
|
||||||
|
auto arm = std::make_shared<FakeActionQueueArm>("action_arm_estop");
|
||||||
|
arm->blockMotion(true);
|
||||||
|
auto queue = makeQueue(arm);
|
||||||
|
api::ActionQueueCommand_Feedback feedback;
|
||||||
|
|
||||||
|
auto execution = std::async(std::launch::async, [&] {
|
||||||
|
queue.execute(twoStepRequest(arm->id()), feedback);
|
||||||
|
});
|
||||||
|
ASSERT_TRUE(arm->waitUntilStarted());
|
||||||
|
arm->triggerHardwareEstop();
|
||||||
|
|
||||||
|
ASSERT_EQ(execution.wait_for(2s), std::future_status::ready);
|
||||||
|
execution.get();
|
||||||
|
EXPECT_EQ(feedback.result(), api::ACTION_RESULT_CODE_FAILED);
|
||||||
|
EXPECT_EQ(arm->calls(), (std::vector<std::string>{"MoveJ"}));
|
||||||
|
EXPECT_TRUE(ControlCommandArbiter::instance().tryAcquireControl());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ActionQueueTest, StepTimeoutStopsBlockedMotion)
|
||||||
|
{
|
||||||
|
auto arm = std::make_shared<FakeActionQueueArm>("action_arm_timeout");
|
||||||
|
arm->blockMotion(true);
|
||||||
|
auto queue = makeQueue(arm);
|
||||||
|
auto request = twoStepRequest(arm->id());
|
||||||
|
request.mutable_steps(0)->set_timeout_ms(30);
|
||||||
|
api::ActionQueueCommand_Feedback feedback;
|
||||||
|
|
||||||
|
queue.execute(request, feedback);
|
||||||
|
|
||||||
|
EXPECT_EQ(feedback.result(), api::ACTION_RESULT_CODE_TIMED_OUT);
|
||||||
|
EXPECT_EQ(feedback.completed_steps(), 0U);
|
||||||
|
EXPECT_EQ(arm->calls(), (std::vector<std::string>{"MoveJ"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace cmvr::service
|
||||||
@ -5,22 +5,31 @@
|
|||||||
#ifndef GRPC_SYSTEM_SERVICE_H
|
#ifndef GRPC_SYSTEM_SERVICE_H
|
||||||
#define GRPC_SYSTEM_SERVICE_H
|
#define GRPC_SYSTEM_SERVICE_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include "cmvr/api/system_service.grpc.pb.h"
|
#include "cmvr/api/system_service.grpc.pb.h"
|
||||||
#include "common/base/grpc_utils.h"
|
#include "common/base/grpc_utils.h"
|
||||||
#include "manager/device_manager/include/device_manager.h"
|
#include "manager/device_manager/include/device_manager.h"
|
||||||
|
|
||||||
namespace cmvr::service
|
namespace cmvr::service
|
||||||
{
|
{
|
||||||
|
class ActionQueue;
|
||||||
|
|
||||||
class gRPCSystemServiceImpl: public api::SystemService::Service {
|
class gRPCSystemServiceImpl: public api::SystemService::Service {
|
||||||
public:
|
public:
|
||||||
gRPCSystemServiceImpl();
|
gRPCSystemServiceImpl();
|
||||||
~gRPCSystemServiceImpl() override = default;
|
~gRPCSystemServiceImpl() override;
|
||||||
|
void prepareForShutdown();
|
||||||
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 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 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;
|
||||||
|
grpc::Status ExecuteActionQueue(grpc::ServerContext* context, const cmvr::api::ActionQueueCommand_Request* request, cmvr::api::ActionQueueCommand_Feedback* response) override;
|
||||||
private:
|
private:
|
||||||
device::DeviceManager& dmgr_;
|
device::DeviceManager& dmgr_;
|
||||||
|
std::unique_ptr<ActionQueue> action_queue_;
|
||||||
|
std::mutex stop_all_mutex_;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,9 @@
|
|||||||
|
|
||||||
#include <google/protobuf/util/time_util.h>
|
#include <google/protobuf/util/time_util.h>
|
||||||
|
|
||||||
|
#include "service/grpc/action/include/control_command_arbiter.h"
|
||||||
|
#include "service/grpc/action/include/control_command_guard.h"
|
||||||
|
|
||||||
using google::protobuf::util::TimeUtil;
|
using google::protobuf::util::TimeUtil;
|
||||||
|
|
||||||
namespace cmvr::service {
|
namespace cmvr::service {
|
||||||
@ -427,6 +430,8 @@ grpc::Status gRPCAgvServiceImpl::clearFault(grpc::ServerContext*,
|
|||||||
const api::CommandHeader_Request* request,
|
const api::CommandHeader_Request* request,
|
||||||
api::CommandHeader_Feedback* response)
|
api::CommandHeader_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
auto agv = dmgr_.getDevice<device::AbstractAGV>(request->device_id());
|
auto agv = dmgr_.getDevice<device::AbstractAGV>(request->device_id());
|
||||||
if (!agv) {
|
if (!agv) {
|
||||||
@ -444,6 +449,8 @@ grpc::Status gRPCAgvServiceImpl::relocalize(
|
|||||||
const api::AgvRelocalizeCommand_Request* request,
|
const api::AgvRelocalizeCommand_Request* request,
|
||||||
api::AgvRelocalizeCommand_Feedback* response)
|
api::AgvRelocalizeCommand_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->header().device_id();
|
const std::string device_id = request->header().device_id();
|
||||||
auto agv = dmgr_.getDevice<device::AbstractAGV>(device_id);
|
auto agv = dmgr_.getDevice<device::AbstractAGV>(device_id);
|
||||||
@ -461,6 +468,8 @@ grpc::Status gRPCAgvServiceImpl::navigateToPose(grpc::ServerContext* context,
|
|||||||
const api::AgvNavigateToPoseCommand_Request* request,
|
const api::AgvNavigateToPoseCommand_Request* request,
|
||||||
api::AgvNavigateToPoseCommand_Feedback* response)
|
api::AgvNavigateToPoseCommand_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
if (context && context->IsCancelled()) {
|
if (context && context->IsCancelled()) {
|
||||||
return setNavigationRequestCanceled(response);
|
return setNavigationRequestCanceled(response);
|
||||||
@ -484,6 +493,8 @@ grpc::Status gRPCAgvServiceImpl::navigateToStation(grpc::ServerContext* context,
|
|||||||
const api::AgvNavigateToStationCommand_Request* request,
|
const api::AgvNavigateToStationCommand_Request* request,
|
||||||
api::AgvNavigateToStationCommand_Feedback* response)
|
api::AgvNavigateToStationCommand_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
if (context && context->IsCancelled()) {
|
if (context && context->IsCancelled()) {
|
||||||
return setNavigationRequestCanceled(response);
|
return setNavigationRequestCanceled(response);
|
||||||
@ -507,6 +518,8 @@ grpc::Status gRPCAgvServiceImpl::followPath(grpc::ServerContext* context,
|
|||||||
const api::AgvFollowPathCommand_Request* request,
|
const api::AgvFollowPathCommand_Request* request,
|
||||||
api::AgvFollowPathCommand_Feedback* response)
|
api::AgvFollowPathCommand_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
if (context && context->IsCancelled()) {
|
if (context && context->IsCancelled()) {
|
||||||
return setNavigationRequestCanceled(response);
|
return setNavigationRequestCanceled(response);
|
||||||
@ -536,6 +549,8 @@ grpc::Status gRPCAgvServiceImpl::pauseNavigation(grpc::ServerContext*,
|
|||||||
const api::CommandHeader_Request* request,
|
const api::CommandHeader_Request* request,
|
||||||
api::CommandHeader_Feedback* response)
|
api::CommandHeader_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
auto agv = dmgr_.getDevice<device::AbstractAGV>(request->device_id());
|
auto agv = dmgr_.getDevice<device::AbstractAGV>(request->device_id());
|
||||||
if (!agv) {
|
if (!agv) {
|
||||||
@ -552,6 +567,8 @@ grpc::Status gRPCAgvServiceImpl::resumeNavigation(grpc::ServerContext*,
|
|||||||
const api::CommandHeader_Request* request,
|
const api::CommandHeader_Request* request,
|
||||||
api::CommandHeader_Feedback* response)
|
api::CommandHeader_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
auto agv = dmgr_.getDevice<device::AbstractAGV>(request->device_id());
|
auto agv = dmgr_.getDevice<device::AbstractAGV>(request->device_id());
|
||||||
if (!agv) {
|
if (!agv) {
|
||||||
@ -584,6 +601,8 @@ grpc::Status gRPCAgvServiceImpl::setVelocity(grpc::ServerContext*,
|
|||||||
const api::AgvSetVelocityCommand_Request* request,
|
const api::AgvSetVelocityCommand_Request* request,
|
||||||
api::AgvSetVelocityCommand_Feedback* response)
|
api::AgvSetVelocityCommand_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->header().device_id();
|
const std::string device_id = request->header().device_id();
|
||||||
auto agv = dmgr_.getDevice<device::AbstractAGV>(device_id);
|
auto agv = dmgr_.getDevice<device::AbstractAGV>(device_id);
|
||||||
@ -602,6 +621,8 @@ grpc::Status gRPCAgvServiceImpl::translate(
|
|||||||
const api::AgvTranslateCommand_Request* request,
|
const api::AgvTranslateCommand_Request* request,
|
||||||
api::AgvTranslateCommand_Feedback* response)
|
api::AgvTranslateCommand_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->header().device_id();
|
const std::string device_id = request->header().device_id();
|
||||||
auto agv = dmgr_.getDevice<device::AbstractAGV>(device_id);
|
auto agv = dmgr_.getDevice<device::AbstractAGV>(device_id);
|
||||||
@ -683,6 +704,8 @@ grpc::Status gRPCAgvServiceImpl::switchMap(grpc::ServerContext*,
|
|||||||
const api::AgvMapCommand_Request* request,
|
const api::AgvMapCommand_Request* request,
|
||||||
api::AgvMapCommand_Feedback* response)
|
api::AgvMapCommand_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->header().device_id();
|
const std::string device_id = request->header().device_id();
|
||||||
auto agv = dmgr_.getDevice<device::AbstractAGV>(device_id);
|
auto agv = dmgr_.getDevice<device::AbstractAGV>(device_id);
|
||||||
@ -700,6 +723,8 @@ grpc::Status gRPCAgvServiceImpl::uploadMap(grpc::ServerContext*,
|
|||||||
const api::AgvMapCommand_Request* request,
|
const api::AgvMapCommand_Request* request,
|
||||||
api::AgvMapCommand_Feedback* response)
|
api::AgvMapCommand_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->header().device_id();
|
const std::string device_id = request->header().device_id();
|
||||||
auto agv = dmgr_.getDevice<device::AbstractAGV>(device_id);
|
auto agv = dmgr_.getDevice<device::AbstractAGV>(device_id);
|
||||||
@ -739,6 +764,8 @@ grpc::Status gRPCAgvServiceImpl::startMapping(grpc::ServerContext*,
|
|||||||
const api::AgvStartMappingCommand_Request* request,
|
const api::AgvStartMappingCommand_Request* request,
|
||||||
api::AgvStartMappingCommand_Feedback* response)
|
api::AgvStartMappingCommand_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->header().device_id();
|
const std::string device_id = request->header().device_id();
|
||||||
auto agv = dmgr_.getDevice<device::AbstractAGV>(device_id);
|
auto agv = dmgr_.getDevice<device::AbstractAGV>(device_id);
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <google/protobuf/util/time_util.h>
|
#include <google/protobuf/util/time_util.h>
|
||||||
|
|
||||||
#include "common/base/logging/logger.h"
|
#include "common/base/logging/logger.h"
|
||||||
|
#include "service/grpc/action/include/control_command_arbiter.h"
|
||||||
|
|
||||||
using google::protobuf::util::TimeUtil;
|
using google::protobuf::util::TimeUtil;
|
||||||
|
|
||||||
@ -119,6 +120,23 @@ grpc::Status setDeviceNotFound(Response* response, const std::string& device_id)
|
|||||||
return grpc::Status(grpc::StatusCode::NOT_FOUND, message);
|
return grpc::Status(grpc::StatusCode::NOT_FOUND, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
grpc::Status setControlBusy(api::CommandHeader_Feedback* response)
|
||||||
|
{
|
||||||
|
const std::string message =
|
||||||
|
"control command rejected while ActionQueue or StopAll is active";
|
||||||
|
fillFeedback(response, false, message);
|
||||||
|
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Response>
|
||||||
|
grpc::Status setControlBusy(Response* response)
|
||||||
|
{
|
||||||
|
const std::string message =
|
||||||
|
"control command rejected while ActionQueue or StopAll is active";
|
||||||
|
fillFeedback(response->mutable_header(), false, message);
|
||||||
|
return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, message);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
gRPCArmServiceImpl::gRPCArmServiceImpl()
|
gRPCArmServiceImpl::gRPCArmServiceImpl()
|
||||||
@ -152,6 +170,11 @@ grpc::Status gRPCArmServiceImpl::torqueOn(grpc::ServerContext*,
|
|||||||
const api::CommandHeader_Request* request,
|
const api::CommandHeader_Request* request,
|
||||||
api::CommandHeader_Feedback* response)
|
api::CommandHeader_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease =
|
||||||
|
ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) {
|
||||||
|
return setControlBusy(response);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->device_id();
|
const std::string device_id = request->device_id();
|
||||||
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
||||||
@ -175,6 +198,11 @@ grpc::Status gRPCArmServiceImpl::clearFault(
|
|||||||
const api::CommandHeader_Request* request,
|
const api::CommandHeader_Request* request,
|
||||||
api::CommandHeader_Feedback* response)
|
api::CommandHeader_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease =
|
||||||
|
ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) {
|
||||||
|
return setControlBusy(response);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->device_id();
|
const std::string device_id = request->device_id();
|
||||||
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
||||||
@ -197,6 +225,11 @@ grpc::Status gRPCArmServiceImpl::moveJ(grpc::ServerContext*,
|
|||||||
const api::MoveJ_Request* request,
|
const api::MoveJ_Request* request,
|
||||||
api::MoveJ_Response* response)
|
api::MoveJ_Response* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease =
|
||||||
|
ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) {
|
||||||
|
return setControlBusy(response);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->header().device_id();
|
const std::string device_id = request->header().device_id();
|
||||||
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
||||||
@ -220,6 +253,11 @@ grpc::Status gRPCArmServiceImpl::moveL(grpc::ServerContext*,
|
|||||||
const api::MoveL_Request* request,
|
const api::MoveL_Request* request,
|
||||||
api::MoveL_Response* response)
|
api::MoveL_Response* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease =
|
||||||
|
ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) {
|
||||||
|
return setControlBusy(response);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->header().device_id();
|
const std::string device_id = request->header().device_id();
|
||||||
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
||||||
@ -320,6 +358,11 @@ grpc::Status gRPCArmServiceImpl::speedJ(grpc::ServerContext*,
|
|||||||
const api::SpeedJ_Request* request,
|
const api::SpeedJ_Request* request,
|
||||||
api::SpeedJ_Response* response)
|
api::SpeedJ_Response* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease =
|
||||||
|
ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) {
|
||||||
|
return setControlBusy(response);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->header().device_id();
|
const std::string device_id = request->header().device_id();
|
||||||
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
||||||
@ -346,6 +389,11 @@ grpc::Status gRPCArmServiceImpl::speedL(grpc::ServerContext*,
|
|||||||
const api::SpeedL_Request* request,
|
const api::SpeedL_Request* request,
|
||||||
api::SpeedL_Response* response)
|
api::SpeedL_Response* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease =
|
||||||
|
ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) {
|
||||||
|
return setControlBusy(response);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->header().device_id();
|
const std::string device_id = request->header().device_id();
|
||||||
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
||||||
@ -373,6 +421,11 @@ grpc::Status gRPCArmServiceImpl::servoJ(grpc::ServerContext*,
|
|||||||
const api::ServoJ_Request* request,
|
const api::ServoJ_Request* request,
|
||||||
api::ServoJ_Response* response)
|
api::ServoJ_Response* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease =
|
||||||
|
ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) {
|
||||||
|
return setControlBusy(response);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->header().device_id();
|
const std::string device_id = request->header().device_id();
|
||||||
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
||||||
@ -472,6 +525,11 @@ grpc::Status gRPCArmServiceImpl::calibrateZeroQ(grpc::ServerContext*,
|
|||||||
const api::CalibrateZeroQ_Request* request,
|
const api::CalibrateZeroQ_Request* request,
|
||||||
api::CalibrateZeroQ_Response* response)
|
api::CalibrateZeroQ_Response* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease =
|
||||||
|
ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) {
|
||||||
|
return setControlBusy(response);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const std::string device_id = request->header().device_id();
|
const std::string device_id = request->header().device_id();
|
||||||
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
auto arm = dmgr_.getDevice<device::RobotArm>(device_id);
|
||||||
|
|||||||
@ -12,6 +12,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "devices/dexhand/rh56dftp_dexhand/include/rh56dftp_dexhand.h"
|
#include "devices/dexhand/rh56dftp_dexhand/include/rh56dftp_dexhand.h"
|
||||||
|
#include "service/grpc/action/include/control_command_arbiter.h"
|
||||||
|
#include "service/grpc/action/include/control_command_guard.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace cmvr::service;
|
using namespace cmvr::service;
|
||||||
@ -227,6 +229,8 @@ grpc::Status gRPCDexHandServiceImpl::GetStatus(grpc::ServerContext* context,
|
|||||||
grpc::Status gRPCDexHandServiceImpl::SetDexHandPos(grpc::ServerContext* context
|
grpc::Status gRPCDexHandServiceImpl::SetDexHandPos(grpc::ServerContext* context
|
||||||
, const cmvr::api::SetDexHandPositionsCommand_Request* request
|
, const cmvr::api::SetDexHandPositionsCommand_Request* request
|
||||||
, cmvr::api::SetDexHandPositionsCommand_Feedback* response) {
|
, cmvr::api::SetDexHandPositionsCommand_Feedback* response) {
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
string dev_id = request->header().device_id();
|
string dev_id = request->header().device_id();
|
||||||
CMVR_LOG(INFO) << "[gRPCDexHandServiceImpl] (SetDexHandPos): id=" << dev_id;
|
CMVR_LOG(INFO) << "[gRPCDexHandServiceImpl] (SetDexHandPos): id=" << dev_id;
|
||||||
@ -264,6 +268,8 @@ grpc::Status gRPCDexHandServiceImpl::SetDexHandPos(grpc::ServerContext* context
|
|||||||
grpc::Status gRPCDexHandServiceImpl::SetDexHandAngle(grpc::ServerContext* context
|
grpc::Status gRPCDexHandServiceImpl::SetDexHandAngle(grpc::ServerContext* context
|
||||||
, const cmvr::api::SetDexHandAnglesCommand_Request* request
|
, const cmvr::api::SetDexHandAnglesCommand_Request* request
|
||||||
, cmvr::api::SetDexHandAnglesCommand_Feedback* response) {
|
, cmvr::api::SetDexHandAnglesCommand_Feedback* response) {
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
string dev_id = request->header().device_id();
|
string dev_id = request->header().device_id();
|
||||||
CMVR_LOG(INFO) << "[gRPCDexHandServiceImpl] (SetDexHandAngle): id=" << dev_id;
|
CMVR_LOG(INFO) << "[gRPCDexHandServiceImpl] (SetDexHandAngle): id=" << dev_id;
|
||||||
@ -305,6 +311,8 @@ grpc::Status gRPCDexHandServiceImpl::SetDexHandAngle(grpc::ServerContext* contex
|
|||||||
grpc::Status gRPCDexHandServiceImpl::SetDexHandForce(grpc::ServerContext* context
|
grpc::Status gRPCDexHandServiceImpl::SetDexHandForce(grpc::ServerContext* context
|
||||||
, const cmvr::api::SetDexHandForceCommand_Request* request
|
, const cmvr::api::SetDexHandForceCommand_Request* request
|
||||||
, cmvr::api::SetDexHandForceCommand_Feedback* response) {
|
, cmvr::api::SetDexHandForceCommand_Feedback* response) {
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
string dev_id = request->header().device_id();
|
string dev_id = request->header().device_id();
|
||||||
CMVR_LOG(INFO) << "[gRPCDexHandServiceImpl] (SetDexHandForce): id=" << dev_id;
|
CMVR_LOG(INFO) << "[gRPCDexHandServiceImpl] (SetDexHandForce): id=" << dev_id;
|
||||||
@ -342,6 +350,8 @@ grpc::Status gRPCDexHandServiceImpl::SetDexHandForce(grpc::ServerContext* contex
|
|||||||
grpc::Status gRPCDexHandServiceImpl::SetDexHandSpeed(grpc::ServerContext* context
|
grpc::Status gRPCDexHandServiceImpl::SetDexHandSpeed(grpc::ServerContext* context
|
||||||
, const cmvr::api::SetDexHandSpeedCommand_Request* request
|
, const cmvr::api::SetDexHandSpeedCommand_Request* request
|
||||||
, cmvr::api::SetDexHandSpeedCommand_Feedback* response) {
|
, cmvr::api::SetDexHandSpeedCommand_Feedback* response) {
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
string dev_id = request->header().device_id();
|
string dev_id = request->header().device_id();
|
||||||
CMVR_LOG(INFO) << "[gRPCDexHandServiceImpl] (SetDexHandSpeed): id=" << dev_id;
|
CMVR_LOG(INFO) << "[gRPCDexHandServiceImpl] (SetDexHandSpeed): id=" << dev_id;
|
||||||
@ -379,6 +389,8 @@ grpc::Status gRPCDexHandServiceImpl::SetDexHandSpeed(grpc::ServerContext* contex
|
|||||||
grpc::Status gRPCDexHandServiceImpl::SetDexHandPresetAct(grpc::ServerContext* context
|
grpc::Status gRPCDexHandServiceImpl::SetDexHandPresetAct(grpc::ServerContext* context
|
||||||
, const cmvr::api::SetDexHandPresetActCommand_Request* request
|
, const cmvr::api::SetDexHandPresetActCommand_Request* request
|
||||||
, cmvr::api::SetDexHandPresetActCommand_Feedback* response) {
|
, cmvr::api::SetDexHandPresetActCommand_Feedback* response) {
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
string dev_id = request->header().device_id();
|
string dev_id = request->header().device_id();
|
||||||
CMVR_LOG(INFO) << "[gRPCDexHandServiceImpl] (SetDexHandPresetAct): id=" << dev_id;
|
CMVR_LOG(INFO) << "[gRPCDexHandServiceImpl] (SetDexHandPresetAct): id=" << dev_id;
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
#include "manager/device_manager/include/device_manager.h"
|
#include "manager/device_manager/include/device_manager.h"
|
||||||
#include "common/base/grpc_utils.h"
|
#include "common/base/grpc_utils.h"
|
||||||
#include "biohead/biohead_esp32/include/biohead_esp32.h"
|
#include "biohead/biohead_esp32/include/biohead_esp32.h"
|
||||||
|
#include "service/grpc/action/include/control_command_arbiter.h"
|
||||||
|
#include "service/grpc/action/include/control_command_guard.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -40,6 +42,9 @@ grpc::Status gRPCMBioHeadServiceImpl::SetExpression(
|
|||||||
const SetFacialExpression_Request* request,
|
const SetFacialExpression_Request* request,
|
||||||
SetFacialExpression_Feedback* response) {
|
SetFacialExpression_Feedback* response) {
|
||||||
|
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::string dev_id = request->header().device_id();
|
std::string dev_id = request->header().device_id();
|
||||||
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
||||||
@ -90,6 +95,8 @@ grpc::Status gRPCMBioHeadServiceImpl::StreamExpression(
|
|||||||
grpc::ServerContext* context,
|
grpc::ServerContext* context,
|
||||||
grpc::ServerReaderWriter<StreamFacialExpression_Feedback, StreamFacialExpression_Request>* stream)
|
grpc::ServerReaderWriter<StreamFacialExpression_Feedback, StreamFacialExpression_Request>* stream)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand();
|
||||||
StreamFacialExpression_Feedback feedback_msg;
|
StreamFacialExpression_Feedback feedback_msg;
|
||||||
std::string dev_id;
|
std::string dev_id;
|
||||||
std::shared_ptr<AbstractBiohead> robot;
|
std::shared_ptr<AbstractBiohead> robot;
|
||||||
@ -268,6 +275,9 @@ grpc::Status gRPCMBioHeadServiceImpl::EmergencyStop(
|
|||||||
grpc::Status gRPCMBioHeadServiceImpl::SpeakStart(grpc::ServerContext* context, const cmvr::api::SpeakStart_Request* request, cmvr::api::SpeakStart_Feedback* response)
|
grpc::Status gRPCMBioHeadServiceImpl::SpeakStart(grpc::ServerContext* context, const cmvr::api::SpeakStart_Request* request, cmvr::api::SpeakStart_Feedback* response)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -326,6 +336,8 @@ grpc::Status gRPCMBioHeadServiceImpl::SpeakStop(grpc::ServerContext* context, co
|
|||||||
|
|
||||||
grpc::Status gRPCMBioHeadServiceImpl::Happy(grpc::ServerContext* context, const cmvr::api::Happy_Request* request, cmvr::api::Happy_Feedback* response)
|
grpc::Status gRPCMBioHeadServiceImpl::Happy(grpc::ServerContext* context, const cmvr::api::Happy_Request* request, cmvr::api::Happy_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
string dev_id = request->header().device_id();
|
string dev_id = request->header().device_id();
|
||||||
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
||||||
@ -350,6 +362,8 @@ grpc::Status gRPCMBioHeadServiceImpl::Happy(grpc::ServerContext* context, const
|
|||||||
|
|
||||||
grpc::Status gRPCMBioHeadServiceImpl::Surprise(grpc::ServerContext* context, const cmvr::api::Surprise_Request* request, cmvr::api::Surprise_Feedback* response)
|
grpc::Status gRPCMBioHeadServiceImpl::Surprise(grpc::ServerContext* context, const cmvr::api::Surprise_Request* request, cmvr::api::Surprise_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
string dev_id = request->header().device_id();
|
string dev_id = request->header().device_id();
|
||||||
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
||||||
@ -375,6 +389,8 @@ grpc::Status gRPCMBioHeadServiceImpl::Surprise(grpc::ServerContext* context, con
|
|||||||
|
|
||||||
grpc::Status gRPCMBioHeadServiceImpl::ExpressionTired(grpc::ServerContext* context, const cmvr::api::ExpressionTired_Request* request, cmvr::api::ExpressionTired_Feedback* response)
|
grpc::Status gRPCMBioHeadServiceImpl::ExpressionTired(grpc::ServerContext* context, const cmvr::api::ExpressionTired_Request* request, cmvr::api::ExpressionTired_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
string dev_id = request->header().device_id();
|
string dev_id = request->header().device_id();
|
||||||
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
||||||
@ -401,6 +417,8 @@ grpc::Status gRPCMBioHeadServiceImpl::ExpressionTired(grpc::ServerContext* conte
|
|||||||
|
|
||||||
grpc::Status gRPCMBioHeadServiceImpl::ExpressionAngry(grpc::ServerContext* context, const cmvr::api::ExpressionAngry_Request* request, cmvr::api::ExpressionAngry_Feedback* response)
|
grpc::Status gRPCMBioHeadServiceImpl::ExpressionAngry(grpc::ServerContext* context, const cmvr::api::ExpressionAngry_Request* request, cmvr::api::ExpressionAngry_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
string dev_id = request->header().device_id();
|
string dev_id = request->header().device_id();
|
||||||
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
||||||
@ -427,6 +445,8 @@ grpc::Status gRPCMBioHeadServiceImpl::ExpressionAngry(grpc::ServerContext* conte
|
|||||||
|
|
||||||
grpc::Status gRPCMBioHeadServiceImpl::ExpressionSadness(grpc::ServerContext* context, const cmvr::api::ExpressionSadness_Request* request, cmvr::api::ExpressionSadness_Feedback* response)
|
grpc::Status gRPCMBioHeadServiceImpl::ExpressionSadness(grpc::ServerContext* context, const cmvr::api::ExpressionSadness_Request* request, cmvr::api::ExpressionSadness_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
string dev_id = request->header().device_id();
|
string dev_id = request->header().device_id();
|
||||||
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
||||||
@ -452,6 +472,8 @@ grpc::Status gRPCMBioHeadServiceImpl::ExpressionSadness(grpc::ServerContext* con
|
|||||||
|
|
||||||
grpc::Status gRPCMBioHeadServiceImpl::ExpressionYawn(grpc::ServerContext* context, const cmvr::api::ExpressionYawn_Request* request, cmvr::api::ExpressionYawn_Feedback* response)
|
grpc::Status gRPCMBioHeadServiceImpl::ExpressionYawn(grpc::ServerContext* context, const cmvr::api::ExpressionYawn_Request* request, cmvr::api::ExpressionYawn_Feedback* response)
|
||||||
{
|
{
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
string dev_id = request->header().device_id();
|
string dev_id = request->header().device_id();
|
||||||
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
auto robot = dmgr_.getDevice<AbstractBiohead>(dev_id);
|
||||||
|
|||||||
@ -14,6 +14,8 @@
|
|||||||
#include "common/base/logging/logger.h"
|
#include "common/base/logging/logger.h"
|
||||||
#include "manager/task_manager/include/task_manager.h"
|
#include "manager/task_manager/include/task_manager.h"
|
||||||
#include "task/touch_screen_task/include/touch_screen_task.h"
|
#include "task/touch_screen_task/include/touch_screen_task.h"
|
||||||
|
#include "service/grpc/action/include/control_command_arbiter.h"
|
||||||
|
#include "service/grpc/action/include/control_command_guard.h"
|
||||||
|
|
||||||
|
|
||||||
using namespace cmvr::service;
|
using namespace cmvr::service;
|
||||||
@ -43,6 +45,8 @@ void fillTouchResponse(Touch_Response* response,
|
|||||||
gRPCHlcServiceImpl::gRPCHlcServiceImpl() = default;
|
gRPCHlcServiceImpl::gRPCHlcServiceImpl() = default;
|
||||||
|
|
||||||
grpc::Status gRPCHlcServiceImpl::touch(grpc::ServerContext *context, const cmvr::api::Touch_Request *request, cmvr::api::Touch_Response *response) {
|
grpc::Status gRPCHlcServiceImpl::touch(grpc::ServerContext *context, const cmvr::api::Touch_Request *request, cmvr::api::Touch_Response *response) {
|
||||||
|
auto control_lease = ControlCommandArbiter::instance().tryAcquireControl();
|
||||||
|
if (!control_lease) return rejectControlCommand(response);
|
||||||
try {
|
try {
|
||||||
auto touch_task = task::TaskManager::getInstance().getTouchScreenTask();
|
auto touch_task = task::TaskManager::getInstance().getTouchScreenTask();
|
||||||
if (!touch_task) {
|
if (!touch_task) {
|
||||||
|
|||||||
@ -5,12 +5,32 @@
|
|||||||
#include "../include/grpc_system_service.h"
|
#include "../include/grpc_system_service.h"
|
||||||
|
|
||||||
#include "common/base/logging/logger.h"
|
#include "common/base/logging/logger.h"
|
||||||
|
#include "service/grpc/action/include/action_queue.h"
|
||||||
|
#include "service/grpc/action/include/control_command_arbiter.h"
|
||||||
|
|
||||||
using namespace cmvr::device;
|
|
||||||
using namespace cmvr::device;
|
using namespace cmvr::device;
|
||||||
using namespace cmvr::service;
|
using namespace cmvr::service;
|
||||||
|
|
||||||
gRPCSystemServiceImpl::gRPCSystemServiceImpl(): dmgr_(DeviceManager::getInstance()) {}
|
gRPCSystemServiceImpl::gRPCSystemServiceImpl()
|
||||||
|
: dmgr_(DeviceManager::getInstance()),
|
||||||
|
action_queue_(std::make_unique<ActionQueue>(
|
||||||
|
[this](const std::string& device_id) {
|
||||||
|
return dmgr_.getDevice<device::RobotArm>(device_id);
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
gRPCSystemServiceImpl::~gRPCSystemServiceImpl()
|
||||||
|
{
|
||||||
|
prepareForShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void gRPCSystemServiceImpl::prepareForShutdown()
|
||||||
|
{
|
||||||
|
if (action_queue_) {
|
||||||
|
action_queue_->shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
grpc::Status gRPCSystemServiceImpl::GetSystemInfo(grpc::ServerContext* context,
|
grpc::Status gRPCSystemServiceImpl::GetSystemInfo(grpc::ServerContext* context,
|
||||||
const api::GetSystemInfoCommand_Request* request, api::GetSystemInfoCommand_Feedback* response)
|
const api::GetSystemInfoCommand_Request* request, api::GetSystemInfoCommand_Feedback* response)
|
||||||
@ -95,6 +115,17 @@ 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)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
std::unique_lock stop_all_lock(stop_all_mutex_, std::try_to_lock);
|
||||||
|
if (!stop_all_lock.owns_lock()) {
|
||||||
|
response->mutable_header()->set_success(false);
|
||||||
|
response->mutable_header()->set_error_message(
|
||||||
|
"another StopAll request is already running");
|
||||||
|
setCurrentTimestamp(
|
||||||
|
response->mutable_header()->mutable_timestamp());
|
||||||
|
return grpc::Status::OK;
|
||||||
|
}
|
||||||
|
auto stop_lease = ControlCommandArbiter::instance().beginStop();
|
||||||
|
action_queue_->cancelAndClear();
|
||||||
dmgr_.stop();
|
dmgr_.stop();
|
||||||
response->mutable_header()->set_success(true);
|
response->mutable_header()->set_success(true);
|
||||||
setCurrentTimestamp(response->mutable_header()->mutable_timestamp());
|
setCurrentTimestamp(response->mutable_header()->mutable_timestamp());
|
||||||
@ -108,3 +139,34 @@ grpc::Status gRPCSystemServiceImpl::StopAll(grpc::ServerContext* context,
|
|||||||
return grpc::Status::OK;
|
return grpc::Status::OK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
grpc::Status gRPCSystemServiceImpl::ExecuteActionQueue(
|
||||||
|
grpc::ServerContext*,
|
||||||
|
const cmvr::api::ActionQueueCommand_Request* request,
|
||||||
|
cmvr::api::ActionQueueCommand_Feedback* response)
|
||||||
|
{
|
||||||
|
if (!request || !response) {
|
||||||
|
return grpc::Status(
|
||||||
|
grpc::StatusCode::INVALID_ARGUMENT,
|
||||||
|
"ActionQueue request and response are required");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
action_queue_->execute(*request, *response);
|
||||||
|
return grpc::Status::OK;
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
response->Clear();
|
||||||
|
response->set_result(api::ACTION_RESULT_CODE_FAILED);
|
||||||
|
response->mutable_header()->set_success(false);
|
||||||
|
response->mutable_header()->set_error_message(error.what());
|
||||||
|
setCurrentTimestamp(response->mutable_header()->mutable_timestamp());
|
||||||
|
return grpc::Status::OK;
|
||||||
|
} catch (...) {
|
||||||
|
response->Clear();
|
||||||
|
response->set_result(api::ACTION_RESULT_CODE_FAILED);
|
||||||
|
response->mutable_header()->set_success(false);
|
||||||
|
response->mutable_header()->set_error_message(
|
||||||
|
"ActionQueue failed with an unknown exception");
|
||||||
|
setCurrentTimestamp(response->mutable_header()->mutable_timestamp());
|
||||||
|
return grpc::Status::OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -11,6 +11,10 @@
|
|||||||
#include "cmvr/config/grpc_server_config/grpc_server_config.pb.h"
|
#include "cmvr/config/grpc_server_config/grpc_server_config.pb.h"
|
||||||
#include "task/task.h"
|
#include "task/task.h"
|
||||||
|
|
||||||
|
namespace cmvr::service {
|
||||||
|
class gRPCSystemServiceImpl;
|
||||||
|
}
|
||||||
|
|
||||||
namespace cmvr::task {
|
namespace cmvr::task {
|
||||||
|
|
||||||
class GrpcServerTask final : public Task {
|
class GrpcServerTask final : public Task {
|
||||||
@ -49,7 +53,7 @@ private:
|
|||||||
std::thread wait_thread_;
|
std::thread wait_thread_;
|
||||||
|
|
||||||
std::unique_ptr<grpc::Service> camera_service_;
|
std::unique_ptr<grpc::Service> camera_service_;
|
||||||
std::unique_ptr<grpc::Service> system_service_;
|
std::unique_ptr<service::gRPCSystemServiceImpl> system_service_;
|
||||||
std::unique_ptr<grpc::Service> speaker_service_;
|
std::unique_ptr<grpc::Service> speaker_service_;
|
||||||
std::unique_ptr<grpc::Service> microphone_service_;
|
std::unique_ptr<grpc::Service> microphone_service_;
|
||||||
std::unique_ptr<grpc::Service> dexhand_service_;
|
std::unique_ptr<grpc::Service> dexhand_service_;
|
||||||
|
|||||||
@ -161,6 +161,9 @@ void GrpcServerTask::stop()
|
|||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::lock_guard lock(mutex_);
|
std::lock_guard lock(mutex_);
|
||||||
|
if (system_service_) {
|
||||||
|
system_service_->prepareForShutdown();
|
||||||
|
}
|
||||||
if (server_) {
|
if (server_) {
|
||||||
server_->Shutdown();
|
server_->Shutdown();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "cmvr/api/arm_command.proto";
|
||||||
import "cmvr/api/common.proto";
|
import "cmvr/api/common.proto";
|
||||||
|
|
||||||
package cmvr.api;
|
package cmvr.api;
|
||||||
@ -69,4 +70,48 @@ message StopAllCommand {
|
|||||||
message Feedback {
|
message Feedback {
|
||||||
CommandHeader.Feedback header = 1;
|
CommandHeader.Feedback header = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ActionResultCode {
|
||||||
|
ACTION_RESULT_CODE_UNSPECIFIED = 0;
|
||||||
|
ACTION_RESULT_CODE_COMPLETED = 1;
|
||||||
|
ACTION_RESULT_CODE_FAILED = 2;
|
||||||
|
ACTION_RESULT_CODE_CANCELED = 3;
|
||||||
|
ACTION_RESULT_CODE_TIMED_OUT = 4;
|
||||||
|
ACTION_RESULT_CODE_REJECTED = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ActionStep {
|
||||||
|
reserved 1;
|
||||||
|
reserved "step_id";
|
||||||
|
|
||||||
|
// Zero inherits the remaining ActionQueue timeout.
|
||||||
|
uint32 timeout_ms = 2;
|
||||||
|
|
||||||
|
oneof command {
|
||||||
|
MoveJ.Request arm_move_j = 10;
|
||||||
|
MoveL.Request arm_move_l = 11;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ActionQueueCommand {
|
||||||
|
message Request {
|
||||||
|
reserved 1;
|
||||||
|
reserved "action_id";
|
||||||
|
|
||||||
|
repeated ActionStep steps = 2;
|
||||||
|
// Zero uses the server default.
|
||||||
|
uint32 total_timeout_ms = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Feedback {
|
||||||
|
CommandHeader.Feedback header = 1;
|
||||||
|
|
||||||
|
reserved 2;
|
||||||
|
reserved "action_id";
|
||||||
|
|
||||||
|
uint32 completed_steps = 3;
|
||||||
|
optional uint32 failed_step_index = 4;
|
||||||
|
ActionResultCode result = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -8,8 +8,7 @@ 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 UpdateParams(UpdateParamsCommand.Request) returns (UpdateParamsCommand.Feedback) {}
|
rpc UpdateParams(UpdateParamsCommand.Request) returns (UpdateParamsCommand.Feedback) {}
|
||||||
|
|
||||||
rpc StopAll(StopAllCommand.Request) returns (StopAllCommand.Feedback) {}
|
rpc StopAll(StopAllCommand.Request) returns (StopAllCommand.Feedback) {}
|
||||||
}
|
rpc ExecuteActionQueue(ActionQueueCommand.Request) returns (ActionQueueCommand.Feedback) {}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user