cmvr-es/cmvr-es/devices/arm/aubo_arm/aubo_arm.cpp
xtkuang 28f1dd1bf8 feat: add gRPC motor control over Modbus TCP
Add synchronous and streaming MotorService APIs backed by the PLC Modbus TCP runtime and protocol driver. Extend AUBO JSON commands and isolate vendor libstdc++ paths while keeping build-tree tests runnable.
2026-07-30 15:09:07 +08:00

1304 lines
46 KiB
C++

#include "devices/arm/aubo_arm/aubo_arm.h"
#include <algorithm>
#include <cctype>
#include <chrono>
#include <cstring>
#include <exception>
#include <thread>
#include <tuple>
#include "common/base/logging/logger.h"
#include "json/json.h"
#include "aubo_sdk/rpc.h"
namespace cmvr::device {
namespace {
struct BusyGuard {
std::atomic<bool>& busy;
~BusyGuard() { busy.store(false); }
};
std::vector<std::string> defaultJointNames(const std::size_t dof)
{
std::vector<std::string> names;
names.reserve(dof);
for (std::size_t i = 0; i < dof; ++i) {
names.push_back("joint_" + std::to_string(i + 1));
}
return names;
}
std::string vendorBrandName(const config::VendorRobotArmBrand brand)
{
switch (brand) {
case config::VENDOR_ROBOT_ARM_BRAND_AUBO_ARM:
return "AuboARM";
case config::VENDOR_ROBOT_ARM_BRAND_UNKNOWN:
default:
return "Unknown";
}
}
using arcs::common_interface::RobotModeType;
using arcs::aubo_sdk::RobotInterfacePtr;
constexpr int kAuboServoMode = 3;
enum class CabinetIoOperation {
GetDigitalInput,
GetDigitalOutput,
SetDigitalOutput,
};
std::string lowerString(std::string value)
{
std::transform(value.begin(), value.end(), value.begin(), [](const unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return value;
}
bool parseJsonCommand(const std::string& request_json,
Json::Value& root,
std::string& error)
{
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
return reader->parse(
request_json.data(),
request_json.data() + request_json.size(),
&root,
&error);
}
std::string compactJson(const Json::Value& value)
{
Json::StreamWriterBuilder builder;
builder[std::string("indentation")] = "";
return Json::writeString(builder, value);
}
Json::Value& jsonMember(Json::Value& root, const char* name)
{
return *root.demand(name, name + std::strlen(name));
}
const Json::Value* findJsonMember(const Json::Value& root, const char* name)
{
return root.find(name, name + std::strlen(name));
}
bool requiredJsonString(const Json::Value& root,
const char* name,
std::string& value)
{
const Json::Value* member = findJsonMember(root, name);
if (!member || !member->isString() || member->asString().empty()) {
return false;
}
value = member->asString();
return true;
}
bool requiredJsonInt(const Json::Value& root, const char* name, int& value)
{
const Json::Value* member = findJsonMember(root, name);
if (!member || !member->isInt()) {
return false;
}
value = member->asInt();
return true;
}
bool requiredJsonBool(const Json::Value& root, const char* name, bool& value)
{
const Json::Value* member = findJsonMember(root, name);
if (!member || !member->isBool()) {
return false;
}
value = member->asBool();
return true;
}
bool parseCabinetIoOperation(const std::string& name, CabinetIoOperation& operation)
{
const std::string normalized = lowerString(name);
if (normalized == "get_di") {
operation = CabinetIoOperation::GetDigitalInput;
} else if (normalized == "get_do") {
operation = CabinetIoOperation::GetDigitalOutput;
} else if (normalized == "set_do") {
operation = CabinetIoOperation::SetDigitalOutput;
} else {
return false;
}
return true;
}
std::string standardOutputRunstateName(
const arcs::common_interface::StandardOutputRunState runstate)
{
return arcs::common_interface::toString(runstate);
}
RobotInterfacePtr getPrimaryRobotInterface(const std::shared_ptr<arcs::aubo_sdk::RpcClient>& rpc_client,
const std::string& context,
Result& result)
{
const auto robot_names = rpc_client->getRobotNames();
if (robot_names.empty()) {
result = Result::failure(ArmErrorCode::RobotNotReady,
"[AuboArm] " + context + " failed: robot name list is empty");
return nullptr;
}
auto robot_interface = rpc_client->getRobotInterface(robot_names.front());
if (!robot_interface) {
result = Result::failure(ArmErrorCode::RobotNotReady,
"[AuboArm] " + context + " failed: robot interface is null");
return nullptr;
}
result = Result::success();
return robot_interface;
}
bool waitForRobotMode(const RobotInterfacePtr& robot_interface,
const RobotModeType& target_mode)
{
const auto start_time = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - start_time < std::chrono::seconds(20)) {
const auto current_mode = robot_interface->getRobotState()->getRobotModeType();
if (current_mode == target_mode) {
return true;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return false;
}
int waitArrival(const RobotInterfacePtr& robot_interface)
{
int retry_count = 0;
int exec_id = robot_interface->getMotionControl()->getExecId();
while (exec_id == -1 && retry_count++ < 5) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
exec_id = robot_interface->getMotionControl()->getExecId();
}
if (exec_id == -1) {
return -1;
}
while (robot_interface->getMotionControl()->getExecId() != -1) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
return 0;
}
bool waitServoModeSelect(const RobotInterfacePtr& robot_interface, const int mode)
{
for (int i = 0; i < 20; ++i) {
if (robot_interface->getMotionControl()->getServoModeSelect() == mode) {
return true;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
return false;
}
CartesianPose poseFromVector(const std::vector<double>& values)
{
CartesianPose pose;
if (values.size() >= 6) {
pose.x = values[0];
pose.y = values[1];
pose.z = values[2];
pose.rx = values[3];
pose.ry = values[4];
pose.rz = values[5];
}
return pose;
}
} // namespace
struct AuboArm::SdkState {
std::shared_ptr<arcs::aubo_sdk::RpcClient> rpc_client;
};
AuboArm::AuboArm(const config::RobotArmConfig& cfg)
: cfg_(cfg)
{
id_ = cfg.id();
if (cfg.has_vendor()) {
vendor_cfg_ = cfg.vendor();
}
ip_ = vendor_cfg_.ip();
port_ = vendor_cfg_.port() > 0 ? vendor_cfg_.port() : 30004;
username_ = vendor_cfg_.username().empty() ? "aubo" : vendor_cfg_.username();
password_ = vendor_cfg_.password().empty() ? "123456" : vendor_cfg_.password();
const auto dof = vendor_cfg_.dof() > 0 ? static_cast<std::size_t>(vendor_cfg_.dof()) : 6U;
model_.name = vendor_cfg_.model().empty() ? "AuboARM" : vendor_cfg_.model();
model_.manufacturer = vendorBrandName(vendor_cfg_.brand());
model_.dof = dof;
model_.joint_names.assign(vendor_cfg_.joint_names().begin(), vendor_cfg_.joint_names().end());
if (model_.joint_names.empty()) {
model_.joint_names = defaultJointNames(dof);
}
if (model_.joint_names.size() != dof) {
CMVR_LOG(ERROR) << "[AuboArm] joint_names size mismatch, id=" << id_;
model_.joint_names = defaultJointNames(dof);
}
}
AuboArm::~AuboArm()
{
(void)disconnect();
}
bool AuboArm::init()
{
if (ip_.empty()) {
CMVR_LOG(ERROR) << "[AuboArm] ip is empty, id=" << id_;
return false;
}
const auto result = connect(ip_, port_);
if (!result.ok()) {
CMVR_LOG(ERROR) << "[AuboArm] init failed: " << result.message;
return false;
}
return true;
}
bool AuboArm::stop()
{
return stopMotion().ok();
}
bool AuboArm::executeJsonCommand(const std::string& request_json,
std::string& response_json)
{
Json::Value response(Json::objectValue);
jsonMember(response, "success") = false;
const auto fail = [&](const std::string& error_code,
const std::string& error_message) {
jsonMember(response, "success") = false;
jsonMember(response, "error_code") = error_code;
jsonMember(response, "error_message") = error_message;
response_json = compactJson(response);
return false;
};
Json::Value root;
std::string parse_error;
if (!parseJsonCommand(request_json, root, parse_error)) {
return fail("invalid_json", "invalid json: " + parse_error);
}
if (!root.isObject()) {
return fail("invalid_json", "invalid json: root must be an object");
}
std::string command;
if (!requiredJsonString(root, "command", command)) {
return fail("invalid_argument",
"field 'command' is required and must be a non-empty string");
}
command = lowerString(command);
if (command != "cabinet_io") {
return fail("unsupported_command", "unsupported json command: " + command);
}
jsonMember(response, "command") = command;
std::string operation_name;
if (!requiredJsonString(root, "operation", operation_name)) {
return fail("invalid_argument",
"field 'operation' is required and must be a non-empty string");
}
operation_name = lowerString(operation_name);
CabinetIoOperation operation{};
if (!parseCabinetIoOperation(operation_name, operation)) {
return fail("invalid_operation",
"unsupported cabinet_io operation: " + operation_name);
}
jsonMember(response, "operation") = operation_name;
int index = -1;
if (!requiredJsonInt(root, "index", index) || index < 0) {
return fail("invalid_argument",
"field 'index' is required and must be a non-negative JSON integer");
}
jsonMember(response, "index") = index;
bool output_value = false;
if (operation == CabinetIoOperation::SetDigitalOutput &&
!requiredJsonBool(root, "value", output_value)) {
return fail("invalid_argument",
"field 'value' is required for set_do and must be a JSON boolean");
}
std::lock_guard lock(mutex_);
const auto ready = ensureConnected_("cabinet_io");
if (!ready.ok()) {
return fail("not_connected", ready.message);
}
try {
Result interface_result;
auto robot_interface =
getPrimaryRobotInterface(sdk_->rpc_client, "cabinet_io", interface_result);
if (!interface_result.ok() || !robot_interface) {
return fail("robot_interface_unavailable", interface_result.message);
}
auto io = robot_interface->getIoControl();
if (!io) {
return fail("io_interface_unavailable",
"[AuboArm] cabinet_io failed: IO interface is null");
}
const bool is_input = operation == CabinetIoOperation::GetDigitalInput;
const int count = is_input
? io->getStandardDigitalInputNum()
: io->getStandardDigitalOutputNum();
jsonMember(response, "count") = count;
if (index >= count) {
return fail(
"index_out_of_range",
"[AuboArm] cabinet_io index out of range: index=" +
std::to_string(index) + ", count=" + std::to_string(count));
}
if (operation == CabinetIoOperation::GetDigitalInput) {
jsonMember(response, "value") = io->getStandardDigitalInput(index);
} else {
const auto runstate = io->getStandardDigitalOutputRunstate(index);
jsonMember(response, "runstate") = standardOutputRunstateName(runstate);
jsonMember(response, "runstate_code") = static_cast<int>(runstate);
if (operation == CabinetIoOperation::GetDigitalOutput) {
jsonMember(response, "value") =
io->getStandardDigitalOutput(index);
} else {
if (runstate != arcs::common_interface::StandardOutputRunState::None) {
return fail(
"output_managed_by_runstate",
"[AuboArm] cabinet_io set_do rejected: output is managed by "
"controller runstate; configure this channel as None before writing");
}
const int ret = io->setStandardDigitalOutput(index, output_value);
jsonMember(response, "sdk_return_code") = ret;
if (ret != 0) {
return fail(
"sdk_command_failed",
"[AuboArm] cabinet_io set_do failed: sdk ret=" +
std::to_string(ret));
}
jsonMember(response, "requested_value") = output_value;
}
}
jsonMember(response, "success") = true;
response_json = compactJson(response);
return true;
} catch (const arcs::common_interface::AuboException& e) {
jsonMember(response, "sdk_return_code") = e.code();
return fail("sdk_exception",
std::string("[AuboArm] cabinet_io failed: ") + e.what());
} catch (const std::exception& e) {
return fail("sdk_exception",
std::string("[AuboArm] cabinet_io failed: ") + e.what());
}
}
ArmState AuboArm::getRobotState() const
{
ArmState state;
state.connected = connected_.load();
state.powered_on = state.connected;
state.brake_released = state.connected;
state.moving = busy_.load();
state.robot_mode = getRobotMode();
state.safety_mode = getSafetyMode();
state.control_mode = getControlMode();
state.emergency_stopped = emergency_stopped_;
state.speed_scaling = speed_scaling_;
state.actual_joint_state = getJointState();
state.target_joint_state = state.actual_joint_state;
return state;
}
JointGroupState AuboArm::getJointState() const
{
JointGroupState state;
state.position.assign(model_.dof, 0.0);
state.velocity.assign(model_.dof, 0.0);
state.effort.assign(model_.dof, 0.0);
if (!connected_.load() || !sdk_ || !sdk_->rpc_client) {
return state;
}
try {
const auto robot_names = sdk_->rpc_client->getRobotNames();
if (robot_names.empty()) {
CMVR_LOG(ERROR) << "[AuboArm] getJointState failed: robot name list is empty";
return state;
}
auto robot_interface = sdk_->rpc_client->getRobotInterface(robot_names.front());
if (!robot_interface) {
CMVR_LOG(ERROR) << "[AuboArm] getJointState failed: robot interface is null";
return state;
}
const auto robot_state = robot_interface->getRobotState();
const auto positions = robot_state->getJointPositions();
const auto velocities = robot_state->getJointSpeeds();
const auto n = std::min<std::size_t>(model_.dof, positions.size());
for (std::size_t i = 0; i < n; ++i) {
state.position[i] = positions[i];
}
const auto vn = std::min<std::size_t>(model_.dof, velocities.size());
for (std::size_t i = 0; i < vn; ++i) {
state.velocity[i] = velocities[i];
}
} catch (const std::exception& e) {
CMVR_LOG(ERROR) << "[AuboArm] getJointState failed: " << e.what();
}
return state;
}
CartesianPose AuboArm::getTcpPose(FrameType frame) const
{
(void)frame;
CartesianPose pose;
if (!connected_.load() || !sdk_ || !sdk_->rpc_client) {
return pose;
}
try {
const auto robot_names = sdk_->rpc_client->getRobotNames();
if (robot_names.empty()) {
CMVR_LOG(ERROR) << "[AuboArm] getTcpPose failed: robot name list is empty";
return pose;
}
auto robot_interface = sdk_->rpc_client->getRobotInterface(robot_names.front());
if (!robot_interface) {
CMVR_LOG(ERROR) << "[AuboArm] getTcpPose failed: robot interface is null";
return pose;
}
const auto pose_values = robot_interface->getRobotState()->getTcpPose();
if (pose_values.size() >= 6) {
pose.x = pose_values[0];
pose.y = pose_values[1];
pose.z = pose_values[2];
pose.rx = pose_values[3];
pose.ry = pose_values[4];
pose.rz = pose_values[5];
}
} catch (const std::exception& e) {
CMVR_LOG(ERROR) << "[AuboArm] getTcpPose failed: " << e.what();
}
return pose;
}
RobotMode AuboArm::getRobotMode() const
{
if (!connected_.load()) {
return RobotMode::Disconnected;
}
if (emergency_stopped_) {
return RobotMode::Stopped;
}
return busy_.load() ? RobotMode::Running : RobotMode::Idle;
}
Result AuboArm::torqueOn()
{
const auto ready = ensureConnected_("torqueOn");
if (!ready.ok()) {
return ready;
}
try {
const auto robot_names = sdk_->rpc_client->getRobotNames();
if (robot_names.empty()) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] robot name list is empty");
}
auto robot_interface = sdk_->rpc_client->getRobotInterface(robot_names.front());
if (!robot_interface) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] robot interface is null");
}
double mass = 0.0;
std::vector<double> cog(3, 0.0);
std::vector<double> aom(3, 0.0);
std::vector<double> inertia(6, 0.0);
robot_interface->getRobotConfig()->setPayload(mass, cog, aom, inertia);
const auto current_mode = robot_interface->getRobotState()->getRobotModeType();
if (current_mode != arcs::common_interface::RobotModeType::Running) {
robot_interface->getRobotManage()->poweron();
if (!waitForRobotMode(robot_interface, arcs::common_interface::RobotModeType::Idle)) {
return Result::failure(ArmErrorCode::CommandFailed, "[AuboArm] torqueOn failed: timeout waiting for Idle");
}
robot_interface->getRobotManage()->startup();
if (!waitForRobotMode(robot_interface, arcs::common_interface::RobotModeType::Running)) {
return Result::failure(ArmErrorCode::CommandFailed, "[AuboArm] torqueOn failed: timeout waiting for Running");
}
}
emergency_stopped_ = false;
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed, std::string("[AuboArm] torqueOn failed: ") + e.what());
}
}
Result AuboArm::torqueOff()
{
const auto ready = ensureConnected_("torqueOff");
if (!ready.ok()) {
return ready;
}
try {
const auto robot_names = sdk_->rpc_client->getRobotNames();
if (robot_names.empty()) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] robot name list is empty");
}
auto robot_interface = sdk_->rpc_client->getRobotInterface(robot_names.front());
if (!robot_interface) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] robot interface is null");
}
robot_interface->getRobotManage()->poweroff();
if (!waitForRobotMode(robot_interface, arcs::common_interface::RobotModeType::PowerOff)) {
return Result::failure(ArmErrorCode::CommandFailed, "[AuboArm] torqueOff failed: timeout waiting for PowerOff");
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed, std::string("[AuboArm] torqueOff failed: ") + e.what());
}
}
Result AuboArm::calibrateZeroQ(const std::string& joint_name)
{
(void)joint_name;
return unsupported_("calibrateZeroQ");
}
Result AuboArm::emergencyStop()
{
emergency_stopped_ = true;
return stopMotion();
}
Result AuboArm::setSpeedScaling(const double scaling)
{
if (scaling < 0.0 || scaling > 1.0) {
return Result::failure(ArmErrorCode::InvalidArgument, "speed scaling must be in [0, 1]");
}
speed_scaling_ = scaling;
return Result::success();
}
Result AuboArm::moveJ(const JointPositionCommand& target, const MotionOptions& options)
{
std::string error;
if (!validDof_(target.position.size(), error)) {
return Result::failure(ArmErrorCode::InvalidDof, error);
}
const auto ready = ensureConnected_("moveJ");
if (!ready.ok()) {
return ready;
}
if (busy_.exchange(true)) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] arm is busy: " + id_);
}
BusyGuard busy_guard{busy_};
try {
const auto robot_names = sdk_->rpc_client->getRobotNames();
if (robot_names.empty()) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] robot name list is empty");
}
auto robot_interface = sdk_->rpc_client->getRobotInterface(robot_names.front());
if (!robot_interface) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] robot interface is null");
}
robot_interface->getMotionControl()->setSpeedFraction(speed_scaling_);
robot_interface->getMotionControl()->moveJoint(
target.position,
options.acceleration > 0.0 ? options.acceleration : 0.5,
options.velocity > 0.0 ? options.velocity : 0.5,
options.blend_radius,
0);
if (waitArrival(robot_interface) != 0) {
return Result::failure(ArmErrorCode::CommandFailed, "[AuboArm] moveJ did not complete");
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed, std::string("[AuboArm] moveJ failed: ") + e.what());
}
}
Result AuboArm::speedJ(const JointVelocityCommand& velocity, double acceleration, double duration)
{
std::string error;
if (!validDof_(velocity.velocity.size(), error)) {
return Result::failure(ArmErrorCode::InvalidDof, error);
}
const auto ready = ensureConnected_("speedJ");
if (!ready.ok()) {
return ready;
}
if (busy_.exchange(true)) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] arm is busy: " + id_);
}
BusyGuard busy_guard{busy_};
try {
Result interface_result;
auto robot_interface = getPrimaryRobotInterface(sdk_->rpc_client, "speedJ", interface_result);
if (!interface_result.ok()) {
return interface_result;
}
robot_interface->getMotionControl()->setSpeedFraction(speed_scaling_);
const double resolved_acceleration = acceleration > 0.0 ? acceleration : 1.5;
const double resolved_duration = duration > 0.0 ? duration : 100.0;
const int ret = robot_interface->getMotionControl()->speedJoint(
velocity.velocity,
resolved_acceleration,
resolved_duration);
if (ret != 0) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] speedJ failed: ret=" + std::to_string(ret));
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed, std::string("[AuboArm] speedJ failed: ") + e.what());
}
}
Result AuboArm::stopJ(double acceleration)
{
if (!connected_.load() || !sdk_ || !sdk_->rpc_client) {
return Result::success();
}
try {
Result interface_result;
auto robot_interface = getPrimaryRobotInterface(sdk_->rpc_client, "stopJ", interface_result);
if (!interface_result.ok()) {
return interface_result;
}
const double resolved_acceleration = acceleration > 0.0 ? acceleration : 31.0;
const int ret = robot_interface->getMotionControl()->stopJoint(resolved_acceleration);
busy_.store(false);
if (ret != 0) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] stopJ failed: ret=" + std::to_string(ret));
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed, std::string("[AuboArm] stopJ failed: ") + e.what());
}
}
Result AuboArm::moveL(const CartesianPose& target, const MotionOptions& options, FrameType frame)
{
(void)frame;
const auto ready = ensureConnected_("moveL");
if (!ready.ok()) {
return ready;
}
if (busy_.exchange(true)) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] arm is busy: " + id_);
}
BusyGuard busy_guard{busy_};
try {
const auto robot_names = sdk_->rpc_client->getRobotNames();
if (robot_names.empty()) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] robot name list is empty");
}
auto robot_interface = sdk_->rpc_client->getRobotInterface(robot_names.front());
if (!robot_interface) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] robot interface is null");
}
robot_interface->getMotionControl()->setSpeedFraction(speed_scaling_);
std::vector<double> tcp_offset(6, 0.0);
robot_interface->getRobotConfig()->setTcpOffset(tcp_offset);
std::vector<double> pose{target.x, target.y, target.z, target.rx, target.ry, target.rz};
robot_interface->getMotionControl()->moveLine(
pose,
options.acceleration > 0.0 ? options.acceleration : 0.5,
options.velocity > 0.0 ? options.velocity : 0.25,
options.blend_radius,
0);
if (waitArrival(robot_interface) != 0) {
return Result::failure(ArmErrorCode::CommandFailed, "[AuboArm] moveL did not complete");
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed, std::string("[AuboArm] moveL failed: ") + e.what());
}
}
Result AuboArm::speedL(const CartesianVelocity& velocity, double acceleration, double duration, FrameType frame)
{
const auto ready = ensureConnected_("speedL");
if (!ready.ok()) {
return ready;
}
if (busy_.exchange(true)) {
return Result::failure(ArmErrorCode::RobotNotReady, "[AuboArm] arm is busy: " + id_);
}
BusyGuard busy_guard{busy_};
try {
Result interface_result;
auto robot_interface = getPrimaryRobotInterface(sdk_->rpc_client, "speedL", interface_result);
if (!interface_result.ok()) {
return interface_result;
}
robot_interface->getMotionControl()->setSpeedFraction(speed_scaling_);
std::vector<double> tcp_offset(6, 0.0);
robot_interface->getRobotConfig()->setTcpOffset(tcp_offset);
std::vector<double> line_speed{velocity.vx, velocity.vy, velocity.vz, 0.0, 0.0, 0.0};
std::vector<double> angular_speed{velocity.wx, velocity.wy, velocity.wz, 0.0, 0.0, 0.0};
if (frame == FrameType::Tool) {
auto tool_frame = robot_interface->getRobotState()->getTcpPose();
if (tool_frame.size() < 6) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] speedL failed: tcp pose size is less than 6");
}
tool_frame[0] = 0.0;
tool_frame[1] = 0.0;
tool_frame[2] = 0.0;
line_speed = sdk_->rpc_client->getMath()->poseTrans(tool_frame, line_speed);
angular_speed = sdk_->rpc_client->getMath()->poseTrans(tool_frame, angular_speed);
} else if (frame == FrameType::User) {
return Result::failure(ArmErrorCode::UnsupportedCommand,
"[AuboArm] speedL User frame requires a configured user coordinate frame");
}
std::vector<double> speed{
line_speed[0],
line_speed[1],
line_speed[2],
angular_speed[0],
angular_speed[1],
angular_speed[2],
};
const double resolved_acceleration = acceleration > 0.0 ? acceleration : 1.2;
const double resolved_duration = duration > 0.0 ? duration : 100.0;
const int ret = robot_interface->getMotionControl()->speedLine(
speed,
resolved_acceleration,
resolved_duration);
if (ret != 0) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] speedL failed: ret=" + std::to_string(ret));
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed, std::string("[AuboArm] speedL failed: ") + e.what());
}
}
Result AuboArm::stopL(std::optional<double> acceleration)
{
if (!connected_.load() || !sdk_ || !sdk_->rpc_client) {
return Result::success();
}
try {
Result interface_result;
auto robot_interface = getPrimaryRobotInterface(sdk_->rpc_client, "stopL", interface_result);
if (!interface_result.ok()) {
return interface_result;
}
const double resolved_acceleration =
acceleration.has_value() && *acceleration > 0.0 ? *acceleration : 10.0;
const int ret = robot_interface->getMotionControl()->stopLine(resolved_acceleration, resolved_acceleration);
busy_.store(false);
if (ret != 0) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] stopL failed: ret=" + std::to_string(ret));
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed, std::string("[AuboArm] stopL failed: ") + e.what());
}
}
Result AuboArm::stopMotion()
{
if (!connected_.load() || !sdk_ || !sdk_->rpc_client) {
return Result::success();
}
try {
const auto robot_names = sdk_->rpc_client->getRobotNames();
if (robot_names.empty()) {
return Result::success();
}
auto robot_interface = sdk_->rpc_client->getRobotInterface(robot_names.front());
if (robot_interface) {
robot_interface->getMotionControl()->stopMove(true, true);
}
busy_.store(false);
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed, std::string("[AuboArm] stopMotion failed: ") + e.what());
}
}
Result AuboArm::startServoMode(const ServoOptions& options)
{
const auto ready = ensureConnected_("startServoMode");
if (!ready.ok()) {
return ready;
}
try {
Result interface_result;
auto robot_interface = getPrimaryRobotInterface(sdk_->rpc_client, "startServoMode", interface_result);
if (!interface_result.ok()) {
return interface_result;
}
const int ret = robot_interface->getMotionControl()->setServoModeSelect(kAuboServoMode);
if (ret != 0) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] startServoMode failed: ret=" + std::to_string(ret));
}
if (!waitServoModeSelect(robot_interface, kAuboServoMode)) {
return Result::failure(ArmErrorCode::Timeout,
"[AuboArm] startServoMode failed: timeout waiting for servo mode");
}
servo_options_ = options;
servo_mode_.store(true);
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed,
std::string("[AuboArm] startServoMode failed: ") + e.what());
}
}
Result AuboArm::servoJ(const JointPositionCommand& target)
{
std::string error;
if (!validDof_(target.position.size(), error)) {
return Result::failure(ArmErrorCode::InvalidDof, error);
}
const auto ready = ensureConnected_("servoJ");
if (!ready.ok()) {
return ready;
}
try {
Result interface_result;
auto robot_interface = getPrimaryRobotInterface(sdk_->rpc_client, "servoJ", interface_result);
if (!interface_result.ok()) {
return interface_result;
}
if (!servo_mode_.load() && robot_interface->getMotionControl()->getServoModeSelect() == 0) {
const auto start_result = startServoMode(servo_options_);
if (!start_result.ok()) {
return start_result;
}
}
const double period = servo_options_.period > 0.0 ? servo_options_.period : 0.008;
const int ret = robot_interface->getMotionControl()->servoJoint(
target.position,
0.0,
0.0,
period,
servo_options_.lookahead_time,
servo_options_.gain);
if (ret != 0) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] servoJ failed: ret=" + std::to_string(ret));
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed, std::string("[AuboArm] servoJ failed: ") + e.what());
}
}
Result AuboArm::servoL(const CartesianPose& target, FrameType frame)
{
const auto ready = ensureConnected_("servoL");
if (!ready.ok()) {
return ready;
}
try {
Result interface_result;
auto robot_interface = getPrimaryRobotInterface(sdk_->rpc_client, "servoL", interface_result);
if (!interface_result.ok()) {
return interface_result;
}
if (!servo_mode_.load() && robot_interface->getMotionControl()->getServoModeSelect() == 0) {
const auto start_result = startServoMode(servo_options_);
if (!start_result.ok()) {
return start_result;
}
}
std::vector<double> pose{target.x, target.y, target.z, target.rx, target.ry, target.rz};
if (frame == FrameType::Tool) {
const auto current_pose = robot_interface->getRobotState()->getTcpPose();
if (current_pose.size() < 6) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] servoL failed: tcp pose size is less than 6");
}
pose = sdk_->rpc_client->getMath()->poseTrans(current_pose, pose);
} else if (frame == FrameType::User) {
return Result::failure(ArmErrorCode::UnsupportedCommand,
"[AuboArm] servoL User frame requires a configured user coordinate frame");
}
const double period = servo_options_.period > 0.0 ? servo_options_.period : 0.008;
const int ret = robot_interface->getMotionControl()->servoCartesian(
pose,
0.0,
0.0,
period,
servo_options_.lookahead_time,
servo_options_.gain);
if (ret != 0) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] servoL failed: ret=" + std::to_string(ret));
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed, std::string("[AuboArm] servoL failed: ") + e.what());
}
}
Result AuboArm::servoSpeedJ(const JointVelocityCommand& velocity)
{
const double period = servo_options_.period > 0.0 ? servo_options_.period : 0.008;
return speedJ(velocity, 1.5, period);
}
Result AuboArm::servoSpeedL(const CartesianVelocity& velocity, FrameType frame)
{
const double period = servo_options_.period > 0.0 ? servo_options_.period : 0.008;
return speedL(velocity, 1.2, period, frame);
}
Result AuboArm::stopServoMode()
{
if (!connected_.load() || !sdk_ || !sdk_->rpc_client) {
servo_mode_.store(false);
return Result::success();
}
try {
Result interface_result;
auto robot_interface = getPrimaryRobotInterface(sdk_->rpc_client, "stopServoMode", interface_result);
if (!interface_result.ok()) {
return interface_result;
}
const int ret = robot_interface->getMotionControl()->setServoModeSelect(0);
servo_mode_.store(false);
if (ret != 0) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] stopServoMode failed: ret=" + std::to_string(ret));
}
if (!waitServoModeSelect(robot_interface, 0)) {
return Result::failure(ArmErrorCode::Timeout,
"[AuboArm] stopServoMode failed: timeout waiting for servo mode disabled");
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed,
std::string("[AuboArm] stopServoMode failed: ") + e.what());
}
}
Result AuboArm::connect(const std::string& ip, const int port)
{
std::lock_guard lock(mutex_);
if (connected_.load()) {
return Result::success();
}
if (ip.empty()) {
return Result::failure(ArmErrorCode::InvalidArgument, "[AuboArm] ip is empty");
}
try {
const int resolved_port = port > 0 ? port : 30004;
auto sdk_state = std::make_unique<SdkState>();
sdk_state->rpc_client = std::shared_ptr<arcs::aubo_sdk::RpcClient>(
::createRpcClient(),
[](arcs::aubo_sdk::RpcClient* client) {
if (client) {
::destroyRpcClient(client);
}
});
if (!sdk_state->rpc_client) {
return Result::failure(ArmErrorCode::ConnectionFailed,
"[AuboArm] connect failed: create RPC client failed");
}
sdk_state->rpc_client->setRequestTimeout(1000);
int ret = sdk_state->rpc_client->connect(ip, resolved_port);
if (ret != 0) {
return Result::failure(ArmErrorCode::ConnectionFailed,
"[AuboArm] connect failed: rpc connect ret=" +
std::to_string(ret) + ", ip=" + ip +
", port=" + std::to_string(resolved_port));
}
ret = sdk_state->rpc_client->login(username_, password_);
if (ret != 0) {
if (sdk_state->rpc_client->hasConnected()) {
sdk_state->rpc_client->disconnect();
}
return Result::failure(ArmErrorCode::ConnectionFailed,
"[AuboArm] connect failed: login ret=" + std::to_string(ret));
}
const auto robot_names = sdk_state->rpc_client->getRobotNames();
if (robot_names.empty()) {
if (sdk_state->rpc_client->hasLogined()) {
sdk_state->rpc_client->logout();
}
if (sdk_state->rpc_client->hasConnected()) {
sdk_state->rpc_client->disconnect();
}
return Result::failure(ArmErrorCode::ConnectionFailed,
"[AuboArm] connect failed: robot name list is empty");
}
ip_ = ip;
port_ = resolved_port;
sdk_ = std::move(sdk_state);
connected_.store(true);
return Result::success();
} catch (const std::exception& e) {
sdk_.reset();
connected_.store(false);
return Result::failure(ArmErrorCode::ConnectionFailed,
std::string("[AuboArm] connect failed: ") + e.what());
}
}
Result AuboArm::disconnect()
{
std::lock_guard lock(mutex_);
try {
if (sdk_ && sdk_->rpc_client) {
if (sdk_->rpc_client->hasLogined()) {
sdk_->rpc_client->logout();
}
if (sdk_->rpc_client->hasConnected()) {
sdk_->rpc_client->disconnect();
}
}
} catch (const std::exception& e) {
CMVR_LOG(ERROR) << "[AuboArm] disconnect failed: " << e.what();
}
sdk_.reset();
connected_.store(false);
busy_.store(false);
servo_mode_.store(false);
return Result::success();
}
Result AuboArm::shutdown()
{
(void)stopMotion();
return disconnect();
}
Result AuboArm::loadProgram(const std::string& program_name)
{
if (program_name.empty()) {
return Result::failure(ArmErrorCode::InvalidArgument, "[AuboArm] loadProgram failed: program name is empty");
}
const auto ready = ensureConnected_("loadProgram");
if (!ready.ok()) {
return ready;
}
try {
const int ret = sdk_->rpc_client->getRuntimeMachine()->loadProgram(program_name);
if (ret != 0) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] loadProgram failed: ret=" + std::to_string(ret));
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed,
std::string("[AuboArm] loadProgram failed: ") + e.what());
}
}
Result AuboArm::playProgram()
{
const auto ready = ensureConnected_("playProgram");
if (!ready.ok()) {
return ready;
}
try {
const int ret = sdk_->rpc_client->getRuntimeMachine()->runProgram();
if (ret != 0) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] playProgram failed: ret=" + std::to_string(ret));
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed,
std::string("[AuboArm] playProgram failed: ") + e.what());
}
}
Result AuboArm::pauseProgram()
{
const auto ready = ensureConnected_("pauseProgram");
if (!ready.ok()) {
return ready;
}
try {
const int ret = sdk_->rpc_client->getRuntimeMachine()->pause();
if (ret != 0) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] pauseProgram failed: ret=" + std::to_string(ret));
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed,
std::string("[AuboArm] pauseProgram failed: ") + e.what());
}
}
Result AuboArm::stopProgram()
{
const auto ready = ensureConnected_("stopProgram");
if (!ready.ok()) {
return ready;
}
try {
const int ret = sdk_->rpc_client->getRuntimeMachine()->abort();
busy_.store(false);
if (ret != 0) {
return Result::failure(ArmErrorCode::CommandFailed,
"[AuboArm] stopProgram failed: ret=" + std::to_string(ret));
}
return Result::success();
} catch (const std::exception& e) {
return Result::failure(ArmErrorCode::CommandFailed,
std::string("[AuboArm] stopProgram failed: ") + e.what());
}
}
std::vector<double> AuboArm::ik(const std::string& base_link,
const std::string& ee_link,
const CartesianPose& pose)
{
(void)base_link;
(void)ee_link;
if (!connected_.load() || !sdk_ || !sdk_->rpc_client) {
CMVR_LOG(ERROR) << "[AuboArm] ik failed: arm is not connected";
return {};
}
try {
Result interface_result;
auto robot_interface = getPrimaryRobotInterface(sdk_->rpc_client, "ik", interface_result);
if (!interface_result.ok()) {
CMVR_LOG(ERROR) << interface_result.message;
return {};
}
const auto qnear = getJointState().position;
const std::vector<double> target_pose{pose.x, pose.y, pose.z, pose.rx, pose.ry, pose.rz};
const auto result = robot_interface->getRobotAlgorithm()->inverseKinematics(qnear, target_pose);
const int ret = std::get<1>(result);
if (ret != 0) {
CMVR_LOG(ERROR) << "[AuboArm] ik failed: ret=" << ret;
return {};
}
return std::get<0>(result);
} catch (const std::exception& e) {
CMVR_LOG(ERROR) << "[AuboArm] ik failed: " << e.what();
}
return {};
}
CartesianPose AuboArm::fk(const std::string& base_link, const std::string& ee_link)
{
(void)base_link;
(void)ee_link;
return getTcpPose(FrameType::Base);
}
CartesianPose AuboArm::fk(bool is_tcp)
{
if (!connected_.load() || !sdk_ || !sdk_->rpc_client) {
return {};
}
try {
Result interface_result;
auto robot_interface = getPrimaryRobotInterface(sdk_->rpc_client, "fk", interface_result);
if (!interface_result.ok()) {
CMVR_LOG(ERROR) << interface_result.message;
return {};
}
const auto q = getJointState().position;
if (q.size() != model_.dof) {
CMVR_LOG(ERROR) << "[AuboArm] fk failed: joint state dof mismatch";
return {};
}
const auto result = is_tcp
? robot_interface->getRobotAlgorithm()->forwardKinematics(q)
: robot_interface->getRobotAlgorithm()->forwardToolKinematics(q);
const int ret = std::get<1>(result);
if (ret != 0) {
CMVR_LOG(ERROR) << "[AuboArm] fk failed: ret=" << ret;
return {};
}
return poseFromVector(std::get<0>(result));
} catch (const std::exception& e) {
CMVR_LOG(ERROR) << "[AuboArm] fk failed: " << e.what();
}
return {};
}
Result AuboArm::unsupported_(const std::string& name) const
{
const std::string message = "[AuboArm] " + name + " is not implemented";
CMVR_LOG(ERROR) << message;
return Result::failure(ArmErrorCode::UnsupportedCommand, message);
}
bool AuboArm::validDof_(const std::size_t size, std::string& error) const
{
if (size != model_.dof) {
error = "[AuboArm] command dof mismatch, expected=" + std::to_string(model_.dof) +
", actual=" + std::to_string(size);
CMVR_LOG(ERROR) << error;
return false;
}
return true;
}
Result AuboArm::ensureConnected_(const std::string& context) const
{
if (!connected_.load()) {
return Result::failure(ArmErrorCode::NotConnected,
"[AuboArm] " + context + " failed: arm is not connected");
}
if (!sdk_ || !sdk_->rpc_client) {
return Result::failure(ArmErrorCode::NotConnected,
"[AuboArm] " + context + " failed: SDK client is null");
}
return Result::success();
}
} // namespace cmvr::device