7. 无作业文件运行
本期将介绍如何在不创建、不打开也不执行作业文件的情况下,通过 SDK 直接向机器人下发 MOVJ 运动指令。
程序会先校验本地运动配置,再连接控制器并完成伺服上电、停止状态确认和远程模式切换。目标点通过可达性预检后,程序直接调用 robot_movej 下发运动指令,等待运动完成并显示最终关节位置,最后在安全条件满足时恢复控制器原始模式并断开连接。
本 Demo 与作业文件运行方式的主要区别如下:
- 不调用
job_create、job_open或job_run - 不在控制器中生成
.JBR作业文件 - 运动参数由上位机通过
MoveCmd直接传递给robot_movej - 上位机负责完成上电、模式切换、预检、运动等待和连接清理
⚠️ 安全警告
以下代码会向控制器发送真实 MOVJ 指令,并导致机器人实际运动。执行前必须确认:
- 目标点来源:
MOVJ_TARGET必须来自当前机器人实际示教,禁止直接使用源码中的示例坐标控制其他机器人 - 人员安全: 机器人工作空间和预期路径内无人员、工装干涉或障碍物
- 参数安全: 速度、加速度和减速度适合当前机器人、负载及调试阶段
- 控制模式: 控制器允许 SDK 切换到远程模式并直接下发运动指令
- 可达性限制: 可达性预检只能判断运动学可达,不能代替碰撞、干涉、奇异位形和现场安全检查
- 急停可用: 急停按钮处于可用状态,并在操作人员可触及范围内
建议先使用全零占位配置并保持确认标志为 false,完成仿真、示教和现场安全验证后,再填写真实目标点并启用运行。
1、配置控制器和目标关节点
首先配置控制器 IP 地址和 SDK 服务端口。程序只有连接到正确的控制器,才能安全执行后续上电和运动流程。
/**
* @name 用户必须根据运行环境修改
* 以下配置决定 SDK 能否连接到实际控制器,运行前必须核对。
* @{
*/
const std::string robot_ip = "192.168.3.243"; ///< 实际控制器的 IP 地址。
const std::string robot_port = "6001"; ///< 实际控制器的 SDK 服务端口,必须与控制器配置一致。
/** @} */MOVJ_TARGET 保存七维关节目标数据,TARGET_POSITION_CONFIRMED 是人工安全确认标志。只有目标点来自当前机器人实际示教,并且已经完成路径和现场安全检查后,才能将确认标志设为 true。
/**
* @name 客户需根据本 Demo 修改
* 目标点必须来自当前机器人实际示教;确认点位和现场安全后,才可启用确认标志。
* @{
*/
const std::array<double, 7> MOVJ_TARGET = {
31.752, -33.184, 31.453, 5.255, -45.787, 31.997, 0.0
}; ///< MOVJ 实际示教关节目标点。
constexpr bool TARGET_POSITION_CONFIRMED = true; ///< 仅在目标点完成示教和安全确认后设为 true。
/** @} */源码中的非零坐标只能视为当前示例环境的数据,不能直接用于其他机器人。未填写真实示教点前,应使用以下安全占位格式:
const std::array<double, 7> MOVJ_TARGET = {
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
};
constexpr bool TARGET_POSITION_CONFIRMED = false;2、配置运动和等待参数
MOVJ 速度、加速度和减速度均使用百分比配置。首次调试应使用较低参数,再根据机器人型号、负载和现场条件逐步调整。
POSITION_DISPLAY_TOLERANCE 只用于运动完成后的偏差提示,不参与运动成功或失败判定。
/**
* @name 建议客户修改
* 首次调试建议使用较低速度和较缓加减速度,并按机器人精度调整位置显示容差。
* @{
*/
constexpr double MOVJ_VELOCITY = 10.0; ///< MOVJ 关节运动速度百分比。
constexpr double MOTION_ACCELERATION = 20.0; ///< 运动加速度百分比。
constexpr double MOTION_DECELERATION = 20.0; ///< 运动减速度百分比。
constexpr double POSITION_DISPLAY_TOLERANCE = 0.5; ///< 目标位置与实际位置的显示偏差阈值,单位为度。
/** @} */等待参数用于限制伺服上电和运动完成的最长等待时间,同时控制状态轮询频率与通信重试次数。
/**
* @name 可修改也可保留默认值
* 以下配置控制等待时间、状态轮询和通信容错,默认值适用于一般演示场景。
* @{
*/
constexpr int POWER_ON_TIMEOUT_SECONDS = 90; ///< 等待伺服上电完成的总超时时间,单位为秒。
constexpr int MOTION_TIMEOUT_SECONDS = 30; ///< 等待单次运动完成的总超时时间,单位为秒。
constexpr int POLL_INTERVAL_MS = 100; ///< 伺服及运动状态轮询间隔,单位为毫秒。
constexpr int QUERY_RETRY_INTERVAL_MS = 500; ///< 状态查询失败后的重试间隔,单位为毫秒。
constexpr int MAX_QUERY_FAILURES = 3; ///< 状态查询允许的最大连续失败次数。
constexpr int STOPPED_CONFIRMATION_COUNT = 5; ///< 未观察到运行状态时,判定完成所需的连续停止状态次数。
/** @} */远程模式和关节坐标类型由 SDK 接口语义决定,通常不应修改:
constexpr int REMOTE_MODE = 1; ///< SDK 直接控制使用的远程模式。
constexpr int JOINT_COORD = 0; ///< MOVJ 及关节位置查询使用的关节坐标类型。3、封装 SDK 返回值检查和状态查询重试
程序中的控制接口统一使用 check_sdk_result 检查返回值,状态查询则通过 query_with_retry 处理短暂通信失败。
bool check_sdk_result(Result result, const std::string& operation)
{
if (result == SUCCESS)
{
return true;
}
std::cerr << operation << "失败,错误码:"
<< static_cast<int>(result) << std::endl;
return false;
}
template <typename Query>
bool query_with_retry(const std::string& operation, Query query)
{
for (int attempt = 1; attempt <= MAX_QUERY_FAILURES; ++attempt)
{
const Result result = query();
if (result == SUCCESS)
{
return true;
}
std::cerr << operation << "失败,第 " << attempt << "/"
<< MAX_QUERY_FAILURES << " 次,错误码:"
<< static_cast<int>(result) << std::endl;
if (attempt < MAX_QUERY_FAILURES)
{
std::this_thread::sleep_for(
std::chrono::milliseconds(QUERY_RETRY_INTERVAL_MS));
}
}
std::cerr << operation << "连续失败,停止当前流程" << std::endl;
return false;
}伺服状态、机器人运行状态和控制器模式分别使用轻量包装函数查询。这些函数复用相同的重试策略,并为每类查询提供明确的错误提示。
bool query_servo_state(SOCKETFD fd, int& state)
{
return query_with_retry("获取伺服状态", [&]() {
return get_servo_state(fd, state);
});
}
bool query_running_state(SOCKETFD fd, int& state)
{
return query_with_retry("获取机器人运行状态", [&]() {
return get_robot_running_state(fd, state);
});
}
bool query_current_mode(SOCKETFD fd, int& mode)
{
return query_with_retry("获取机器人当前模式", [&]() {
return get_current_mode(fd, mode);
});
}4、封装本地运动配置校验函数
连接控制器和改变伺服状态之前,程序先检查人工确认标志、速度和加减速度范围,以及目标点中是否存在无穷大或非数字值。
/**
* @brief 校验目标点确认标志、运动参数范围和目标点数值有效性。
* @retval true 本地运动配置通过校验。
* @retval false 目标点未确认、运动参数越界或目标点包含非法数值。
*/
bool validate_motion_configuration()
{
if (!TARGET_POSITION_CONFIRMED)
{
std::cerr << "目标点尚未确认:请填写实际示教关节点,并将 "
<< "TARGET_POSITION_CONFIRMED 设置为 true" << std::endl;
return false;
}
if (MOVJ_VELOCITY <= 0.0 || MOVJ_VELOCITY > 100.0)
{
std::cerr << "MOVJ速度必须位于 (0, 100] 范围内" << std::endl;
return false;
}
if (MOTION_ACCELERATION <= 0.0 || MOTION_ACCELERATION > 100.0
|| MOTION_DECELERATION <= 0.0 || MOTION_DECELERATION > 100.0)
{
std::cerr << "加速度和减速度必须位于 (0, 100] 范围内" << std::endl;
return false;
}
for (double value : MOVJ_TARGET)
{
if (!std::isfinite(value))
{
std::cerr << "目标点包含非法数值" << std::endl;
return false;
}
}
return true;
}该函数只验证本地数据格式和参数范围,不能证明目标点属于当前机器人,也不能替代关节限位、可达性和碰撞风险检查。
5、封装伺服上电和状态确认函数
set_servo_poweron 调用成功只表示上电命令已发送,不代表伺服已经立即进入状态 3。wait_until_servo_running 会持续查询伺服状态,直到确认进入运行状态、检测到报警或未知状态,或者等待超时。
bool wait_until_servo_running(
SOCKETFD fd,
std::chrono::seconds timeout =
std::chrono::seconds(POWER_ON_TIMEOUT_SECONDS))
{
using clock = std::chrono::steady_clock;
const auto deadline = clock::now() + timeout;
int last_state = -1;
while (clock::now() < deadline)
{
if (!query_servo_state(fd, last_state))
return false;
if (last_state == 3)
{
std::cout << "伺服上电完成,已进入运行状态" << std::endl;
return true;
}
if (last_state == 2)
{
std::cerr << "伺服在上电过程中进入报警状态" << std::endl;
return false;
}
if (last_state != 0 && last_state != 1)
{
std::cerr << "控制器返回未知伺服状态:" << last_state << std::endl;
return false;
}
std::this_thread::sleep_for(
std::chrono::milliseconds(POLL_INTERVAL_MS));
}
std::cerr << "等待伺服上电超时,最后状态:" << last_state << std::endl;
return false;
}power_on 根据当前伺服状态选择对应的上电路径:状态 0 先切换到就绪再上电,状态 1 直接上电,状态 3 直接返回成功。
bool power_on(SOCKETFD fd)
{
int servo_state = -1;
if (!query_servo_state(fd, servo_state))
return false;
switch (servo_state)
{
case 0:
if (!check_sdk_result(set_servo_state(fd, 1), "设置伺服为就绪状态"))
return false;
if (!check_sdk_result(set_servo_poweron(fd), "机器人上电"))
return false;
break;
case 1:
if (!check_sdk_result(set_servo_poweron(fd), "机器人上电"))
return false;
break;
case 2:
std::cerr << "伺服处于报警状态,请排除故障并确认安全后再运行"
<< std::endl;
return false;
case 3:
std::cout << "伺服已经处于运行状态,无需重复上电" << std::endl;
return true;
default:
std::cerr << "控制器返回未知伺服状态:" << servo_state << std::endl;
return false;
}
return wait_until_servo_running(fd);
}6、封装远程模式切换函数
SDK 直接下发 MOVJ 需要控制器处于远程模式。switch_to_remote_mode 先保存原始模式,再在需要时切换到模式 1,并重新查询确认切换结果。
bool switch_to_remote_mode(SOCKETFD fd, int& original_mode)
{
if (!query_current_mode(fd, original_mode))
return false;
if (original_mode < 0 || original_mode > 2)
{
std::cerr << "控制器返回未知模式:" << original_mode << std::endl;
return false;
}
if (original_mode == REMOTE_MODE)
{
std::cout << "控制器已处于远程模式" << std::endl;
return true;
}
if (!check_sdk_result(
set_current_mode(fd, REMOTE_MODE),
"切换控制器到远程模式"))
return false;
int confirmed_mode = -1;
if (!query_current_mode(fd, confirmed_mode))
return false;
if (confirmed_mode != REMOTE_MODE)
{
std::cerr << "远程模式确认失败,当前模式:" << confirmed_mode << std::endl;
return false;
}
std::cout << "控制器已切换到远程模式" << std::endl;
return true;
}7、构造 MOVJ 指令并执行可达性预检
make_movej_command 使用已确认的目标点和运动参数填充 MoveCmd。单独封装构造过程可以集中维护坐标类型、速度、加减速度、平滑参数及构型等字段,避免主流程遗漏配置。
MoveCmd make_movej_command()
{
MoveCmd command;
command.targetPosType = PosType::data;
std::copy(MOVJ_TARGET.begin(), MOVJ_TARGET.end(), command.targetPosValue.begin());
command.coord = JOINT_COORD;
command.velocity = MOVJ_VELOCITY;
command.acc = MOTION_ACCELERATION;
command.dec = MOTION_DECELERATION;
command.pl = 0;
command.toolNum = 0;
command.userNum = 0;
command.configuration = 0;
return command;
}可达性接口需要长度为 14 的位置数据,因此 make_reachability_position 将实际 MOVJ 指令转换为接口格式。
std::vector<double> make_reachability_position(const MoveCmd& command)
{
std::vector<double> position(14, 0.0);
position[0] = static_cast<double>(command.coord);
position[1] = 0.0; // 0 表示目标点使用角度制。
position[2] = static_cast<double>(command.configuration);
position[3] = static_cast<double>(command.toolNum);
position[4] = static_cast<double>(command.userNum);
std::copy_n(command.targetPosValue.begin(), 7, position.begin() + 7);
return position;
}precheck_reachability 统一调用 get_pos_reachable、检查 SDK 返回值并解释可达结果。预检失败或目标点不可达时,程序不会发送运动指令。
bool precheck_reachability(SOCKETFD fd, const MoveCmd& command)
{
bool reachable = false;
if (!check_sdk_result(
get_pos_reachable(
fd, make_reachability_position(command), "MOVJ", reachable),
"MOVJ目标点可达性预检"))
{
return false;
}
if (!reachable)
{
std::cerr << "MOVJ目标点不可达,已取消运动" << std::endl;
return false;
}
std::cout << "MOVJ目标点可达性预检通过" << std::endl;
return true;
}可达性通过只表示目标点在运动学上可达,不能证明实际路径没有碰撞或干涉风险。
8、封装运动完成等待和最终位置显示
robot_movej 返回成功只表示运动指令已经下发。wait_for_motion_complete 持续查询机器人运行状态,并处理运行、暂停、停止、未知状态和超时。
bool wait_for_motion_complete(
SOCKETFD fd,
std::chrono::seconds timeout =
std::chrono::seconds(MOTION_TIMEOUT_SECONDS))
{
using clock = std::chrono::steady_clock;
const auto deadline = clock::now() + timeout;
bool running_observed = false;
bool pause_reported = false;
int stopped_count = 0;
while (clock::now() < deadline)
{
int running_state = -1;
if (!query_running_state(fd, running_state))
return false;
switch (running_state)
{
case 2:
running_observed = true;
pause_reported = false;
stopped_count = 0;
break;
case 1:
stopped_count = 0;
if (!pause_reported)
{
std::cout << "机器人运动已暂停,继续等待恢复" << std::endl;
pause_reported = true;
}
break;
case 0:
++stopped_count;
if (running_observed
|| stopped_count >= STOPPED_CONFIRMATION_COUNT)
{
std::cout << "已确认机器人运动完成" << std::endl;
return true;
}
break;
default:
std::cerr << "控制器返回未知运行状态:" << running_state << std::endl;
return false;
}
std::this_thread::sleep_for(
std::chrono::milliseconds(POLL_INTERVAL_MS));
}
std::cerr << "等待机器人运动完成超时,请立即确认机器人状态,必要时按下急停"
<< std::endl;
return false;
}运动完成后,show_final_position 查询当前关节位置并逐轴显示。单独封装该函数可以统一检查返回容器长度和位置偏差,避免读取不完整数据。
bool show_final_position(SOCKETFD fd)
{
std::vector<double> current_position;
if (!query_with_retry("获取运动完成后的关节位置", [&]() {
return get_current_position(fd, JOINT_COORD, current_position);
}))
{
return false;
}
if (current_position.size() < MOVJ_TARGET.size())
{
std::cerr << "控制器返回的关节位置长度不足,实际长度:"
<< current_position.size() << std::endl;
return false;
}
bool within_tolerance = true;
std::cout << "运动完成后的关节位置:";
for (std::size_t index = 0; index < MOVJ_TARGET.size(); ++index)
{
if (index != 0)
std::cout << ", ";
std::cout << current_position[index];
if (std::fabs(current_position[index] - MOVJ_TARGET[index])
> POSITION_DISPLAY_TOLERANCE)
{
within_tolerance = false;
}
}
std::cout << std::endl;
if (!within_tolerance)
{
std::cout << "提示:实际关节位置与目标值存在超过 "
<< POSITION_DISPLAY_TOLERANCE
<< " 度的偏差,请结合机器人精度和轴数量检查"
<< std::endl;
}
return true;
}偏差超过阈值只输出提示,不会将本次位置查询判定为失败。该阈值不是控制器到位判定参数。
9、封装无作业文件运动主流程
run_demo 负责组织全部控制步骤,并在每个关键节点执行安全确认:
- 根据当前伺服状态完成上电。
- 确认机器人当前处于停止状态,避免干扰已有运动。
- 保存原模式并切换到远程模式。
- 构造 MOVJ 指令并执行可达性预检。
- 通过
robot_movej直接下发运动,不创建作业文件。 - 等待运动完成并显示最终关节位置。
- 再次查询运行状态,只有确认停止时才恢复原模式。
bool run_demo(SOCKETFD fd)
{
if (!power_on(fd))
{
std::cerr << "机器人上电失败,不会下发运动指令" << std::endl;
return false;
}
int running_state = -1;
if (!query_running_state(fd, running_state))
return false;
if (running_state != 0)
{
std::cerr << "机器人当前不是停止状态,运行状态码:"
<< running_state << std::endl;
std::cerr << "为避免干扰现有运动,本示例不会下发新指令" << std::endl;
return false;
}
int original_mode = -1;
if (!switch_to_remote_mode(fd, original_mode))
return false;
bool success = true;
const MoveCmd command = make_movej_command();
if (!precheck_reachability(fd, command))
{
success = false;
}
else if (!check_sdk_result(
robot_movej(fd, command),
"直接下发MOVJ运动指令"))
{
success = false;
}
else
{
std::cout << "MOVJ指令已通过SDK直接下发,未创建或使用作业文件"
<< std::endl;
success = wait_for_motion_complete(fd);
if (success)
{
success = show_final_position(fd);
}
}
int final_running_state = -1;
if (!query_running_state(fd, final_running_state))
{
success = false;
}
else if (final_running_state == 0 && original_mode != REMOTE_MODE)
{
if (!check_sdk_result(
set_current_mode(fd, original_mode),
"恢复控制器原始模式"))
{
success = false;
}
}
else if (final_running_state != 0)
{
std::cerr << "机器人仍可能处于运动或暂停状态,未自动切换控制器模式"
<< std::endl;
success = false;
}
return success;
}当最终运行状态不确定,或者机器人仍在运行或暂停时,程序不会自动切换控制器模式,而是保留远程模式并返回失败,提示操作人员先确认机器人实际状态。
10、主程序校验配置、连接并断开控制器
主函数在连接控制器之前调用 validate_motion_configuration,避免无效配置改变真实控制器状态。连接成功后执行无作业运动流程,无论运动流程成功还是失败,最后都会调用 disconnect_robot 并检查断开结果。
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <cpp_interface/nrc_interface.h>
#include "../demo_utils.h"
/**
* @brief Demo 程序入口:校验配置、连接控制器并执行无作业文件的 MOVJ 运动。
* @return Demo 成功且连接正常断开时返回 0,否则返回 1。
*/
int main()
{
demo::enable_console_utf8();
// 在建立连接和上电前验证本地配置,避免使用占位点位改变控制器状态。
if (!validate_motion_configuration())
{
return 1;
}
const SOCKETFD fd = connect_robot(robot_ip, robot_port);
if (fd <= 0)
{
std::cerr << "控制器连接失败" << std::endl;
return 1;
}
std::cout << "控制器连接成功" << std::endl;
bool success = run_demo(fd);
if (!check_sdk_result(disconnect_robot(fd), "断开控制器连接"))
{
success = false;
}
else
{
std::cout << "控制器连接已断开" << std::endl;
}
return success ? 0 : 1;
}