add estop function

This commit is contained in:
NuoDaJia
2026-03-03 17:03:51 +08:00
parent 99b3d4a7a2
commit 2be6713107
13 changed files with 674 additions and 3 deletions

View File

@@ -0,0 +1,492 @@
# MC-S-002\_点到点运动PTP设计方案
# 双臂运动控制设计文档MoveIt 规划 + Action 下发
| 版本 | 日期 | 作者 | 描述 |
| --- | --- | --- | --- |
| v1.0.0 | 2026/02/25 | Ray | 文档新建 |
| v1.0.1 | 2026/02/28 | Ray | 完善对外接口部分 |
## 1. 概述
机械臂需要快速、平滑地从一个位置运动到另一个位置。点到点运动PTP在关节空间进行规划基于 **MoveIt 2** 生成轨迹,并通过左右臂各自的 `**FollowJointTrajectory**` action 下发到 **ros2\_control** 的 `JointTrajectoryController` 执行。目前支持单臂运动和双臂协同PTP运动类型。
## 2. 功能需求
* 在PTP运动规划过程中系统应生成平滑的运动轨迹速度曲线加速度曲线在所有时间点连续无阶跃。
* 在PTP运动执行过程中系统应在轨迹规划失败时返回详细的错误原因帮助上层应用诊断问题。
* 在PTP执行过程中系统应在运动过程中检测到急停或错误时立即停止运动返回中断状态。
* 在PTP执行过程中系统应在执行运动前检测碰撞风险发现碰撞风险时拒绝执行返回碰撞信息。
* 在PTP运动执行过程中系统应在执行运动前验证目标位置是否在工作空间内超出工作空间时返回错误不执行运动。
* 在PTP运动规划过程中系统应在规定时间内完成轨迹规划确保实时响应规划时间  50ms。
* 在PTP运动指令规划执行过程中系统应严格控制实际运动速度确保不超过设定的速度限制支持速度比例参数1%-100%),可动态调整
* 在PTP运动执行过程中系统应在运动完成后通过Action Result通知上层应用包含最终位置和状态信息运动执行结果SUCCESS成功、FAILED失败、CANCELLED取消
* 在PTP运动规划中系统应确保机器人到达目标位置时无振荡到达目标后位置波动范围  ±0.05mm。
## 3. 依赖
* **ROS 2 / rclcpp**
* **MoveIt 2**`move_group``MoveGroupInterface`
* **ros2\_control**`controller_manager``JointTrajectoryController`
* **控制接口**`FollowJointTrajectory` action
---
## 4. 内部结构设计(按当前代码)
### 4.1 总体架构
```mermaid
flowchart LR
subgraph App[上层应用]
A1[DualArm Action Client]
A2["Service Client<br/>IK/Enable/ClearError"]
A3[Status Subscriber]
end
subgraph RC[robot_control]
N[robot_control_node]
AM[ActionManager]
DA[DualArmAction]
RCM[RobotControlManager]
AC[ArmControl]
KS[KinematicsService]
AHS[ArmHardwareService]
ASS[ArmStatusService]
end
subgraph MoveIt[MoveIt 2]
MGI["MoveGroupInterface<br/>arm_left/arm_right/dual_arm"]
RS[RobotState IK/FK]
end
subgraph Ctrl[ros2_control]
L[left_arm_controller]
R[right_arm_controller]
G1[left_arm_gpio_controller]
G2[right_arm_gpio_controller]
end
A1 --> DA
A2 --> KS
A2 --> AHS
A3 --> ASS
N --> AM --> DA
DA --> RCM --> AC --> MGI
KS --> RCM --> AC --> RS
AHS --> G1
AHS --> G2
ASS --> RCM
DA --> L
DA --> R
```
### 4.2 分层职责
#### `robot_control_node`(编排层)
- 负责节点生命周期、订阅/发布器/service/action server 的创建与绑定。
- 保持“薄节点”原则,不承载具体业务算法。
- 启动时激活左右臂控制器并初始化 MoveIt。
#### `ActionManager` + `DualArmAction`PTP主链路
- 对外提供 `DualArm` action。
- 负责单臂/双臂 PTP 目标校验、规划调用、轨迹导出、拆分与并发下发。
- 负责执行期反馈、取消处理和结果回传。
#### `RobotControlManager`(领域能力聚合)
- 聚合运动学、状态映射、控制器状态等核心能力。
- 提供 `PlanArmMotion` / `PlanDualArmJointMotion`、轨迹导出、关节状态查询等统一接口。
- 提供 IK 与 FK 路径:
- IK调用 `ArmControl::SolveInverseKinematics`(纯 IK`setFromIK`
- FK使用当前 `jointPositions_` 计算末端位姿。
#### `ArmControl`MoveIt 适配层)
- 管理 `arm_left` / `arm_right` / `dual_arm``MoveGroupInterface`
- 轨迹规划、时间参数化、轨迹缓存导出。
- 纯 IK 与基于给定关节值的 FK 计算。
#### 新增服务层(`services/`
- `KinematicsService`:对外逆解服务入口,负责请求/响应封装与错误语义。
- `ArmHardwareService`:对外使能与清错服务,负责 GPIO 命令下发和缓存更新。
- `ArmStatusService`:解析 `DynamicJointState`,定时发布 `RobotArmStatus`
> 结论第4版设计从“节点内聚”调整为“节点编排 + 业务服务分层”,便于维护与扩展。
---
## 5. 配置项设计
### 5.1 控制器与MoveIt配置
- `left_arm_controller``right_arm_controller`6轴 `JointTrajectoryController`
- `controller_manager/switch_controller`:启动时由节点激活。
- MoveIt 使用三个 group`arm_left``arm_right``dual_arm`
### 5.2 关键运行参数
- `robot_arm_status_period_ms`:状态发布周期,范围 `[10, 20]` ms默认 `20` ms。
- `velocity_scaling`Action 目标速度比例(兼容 1~100 输入语义)。
### 5.3 主题与服务约定
- GPIO命令
- `/left_arm_gpio_controller/commands`
- `/right_arm_gpio_controller/commands`
- 状态输入:
- `/joint_states`
- `/dynamic_joint_states`
- 状态输出:
- `/robot_control/arm_status`
- 对外服务:
- `/robot_control/inverse_kinematics`
- `/robot_control/set_arm_enable`
- `/robot_control/clear_arm_error`
- `/robot_control/reset_estop`
---
## 6. 接口定义(当前实现)
### 6.1 PTP执行接口Action
- `interfaces/action/DualArm`(当前代码)
```plaintext
# DualArm.action
# 状态常量定义
uint8 STATUS_PLANNING = 0 # 状态: 规划中
uint8 STATUS_EXECUTING = 1 # 状态: 执行中
uint8 STATUS_DONE = 2 # 状态: 完成
# Action Goal: 双臂控制目标
interfaces/ArmMotionParams[] arm_motion_params # 每条手臂的运动参数 (包含目标位姿、轨迹等)
int32 velocity_scaling # 速度百分比系数 [1, 100] (100表示全速)
---
# Action Result: 执行结果
bool success # 执行是否成功 (true=成功, false=失败)
string message # 结果描述信息
float64 final_progress # 最终进度 [0.0, 1.0]
---
# Action Feedback: 实时反馈
float64 progress # 实时总体进度 [0.0, 1.0]
uint8 status # 实时状态 (0:规划中, 1:执行中, 2:完成)
float64[] joints_left # 左臂当前关节角 [单位: 弧度]
float64[] joints_right # 右臂当前关节角 [单位: 弧度]
```
### 6.2 运动学与硬件服务
- `interfaces/srv/InverseKinematics`(当前代码)
```plaintext
int32 arm_id #0-左臂1-右臂
geometry_msgs/Pose pose #目标位姿
---
int32 result #结果0-成功非0-失败
float32[] joint_angles #解算出来的关节角
```
- `interfaces/srv/SetArmEnable`(当前代码)
```plaintext
# SetArmEnable.srv
# 机械臂关节上下使能服务
#
# 用途:
# - 对单关节进行使能/失能
# - 对单臂全部关节进行使能/失能
# - 对双臂全部关节(或同编号关节)进行使能/失能
#
# 参数组合说明:
# - arm_id=0, joint_num=0: 左臂全部关节
# - arm_id=1, joint_num=0: 右臂全部关节
# - arm_id=2, joint_num=0: 双臂全部关节
# - arm_id=0/1, joint_num=1~6: 指定单臂的指定关节
# - arm_id=2, joint_num=1~6: 双臂的同编号关节如左右第3关节
#
# 注意:
# - joint_num=0 表示“该作用范围内的全部关节”
# - 关节编号按 1 开始计数
int8 arm_id # 机械臂ID: 0=左臂, 1=右臂, 2=双臂
int8 joint_num # 关节编号: 1~6=指定关节, 0=全部关节
bool enable # 目标状态: true=使能, false=失能
---
bool success # true=操作成功, false=操作失败
string message # 执行结果描述(失败原因/调试信息)
```
- `interfaces/srv/ClearArmError`(当前代码)
```plaintext
# ClearArmError.srv
# 清除机械臂错误服务
int8 arm_id # 机械臂ID (例如: 0=左臂, 1=右臂, 2=双臂)
int8 joint_num # 关节编号 (1~7表示第1个到第7个关节, 0表示所有关节)
---
bool success # 操作是否成功
string message # 结果信息
```
- `interfaces/srv/ResetEStop`(当前代码)
```plaintext
# ResetEStop.srv
# 急停复位服务(用于清除上层软件锁存态)
#
# 注意:
# - 该服务仅表达“复位请求”,是否允许复位由安全链路策略决定
# - 硬件安全回路未恢复时,应返回 success=false
bool reset # true=请求复位false=无效请求
---
bool success # true=复位成功false=复位失败
string message # 结果描述(失败原因/诊断信息)
```
### 6.3 状态发布接口
- `interfaces/msg/RobotArmStatus`topic: `/robot_control/arm_status`,当前代码)
```plaintext
builtin_interfaces/Time stamp
string[] joint_names
float64[] joint_positions
bool[] joint_enabled
int32[] joint_error_codes
bool estop_latched
uint32 estop_generation
geometry_msgs/Pose left_arm_pose
geometry_msgs/Pose right_arm_pose
```
> 末端位姿采用 `jointPositions_ + FK` 计算,确保与同帧关节值一致。
---
## 7. 行为与流程设计
### 7.1 单臂 PTP 主流程
```mermaid
sequenceDiagram
participant C as Client
participant DA as DualArmAction
participant RCM as RobotControlManager
participant AC as ArmControl
participant FJT as JointTrajectoryController
C->>DA: Goal(单臂MOVEJ)
DA->>DA: 参数/状态校验
DA->>RCM: PlanArmMotion()
RCM->>AC: PlanJointMotion()
AC-->>RCM: 规划轨迹缓存
DA->>RCM: ExportArmPlannedTrajectory()
DA->>FJT: async_send_goal()
FJT-->>DA: result/feedback
DA-->>C: result
```
### 7.2 双臂 PTP 主流程
```mermaid
sequenceDiagram
participant C as Client
participant DA as DualArmAction
participant RCM as RobotControlManager
participant AC as ArmControl(dual_arm)
participant L as left FJT
participant R as right FJT
C->>DA: Goal(双臂)
DA->>RCM: PlanDualArmJointMotion()
RCM->>AC: PlanJointMotion(arm_id=2)
AC-->>RCM: 12轴轨迹
DA->>RCM: ExportDualArmPlannedTrajectory()
DA->>DA: 拆分左右轨迹
par left
DA->>L: send goal
and right
DA->>R: send goal
end
L-->>DA: result
R-->>DA: result
DA-->>C: result
```
### 7.3 服务链路流程
- `InverseKinematics``KinematicsService -> RobotControlManager -> ArmControl(setFromIK)`
- `SetArmEnable/ClearArmError``ArmHardwareService -> GPIO controllers`
- `RobotArmStatus``ArmStatusService` 定时汇总发布(状态输入来自 `joint_states` + `dynamic_joint_states`
---
## 8. 时序与性能设计
### 8.1 PTP链路性能目标
- 规划阶段:以“稳定优先”,不再文档强约束固定 50ms。
- 执行反馈:依赖 action feedback 周期与控制器状态更新频率。
- 状态发布10~20ms 周期参数可调默认20ms
### 8.2 时序一致性策略
- 关节位置:来自 `joint_states` 映射后的 `jointPositions_`
- 末端位姿:基于同一时刻 `jointPositions_` 进行 FK。
- 使能/错误:优先由 `dynamic_joint_states` 更新,服务操作会同步更新缓存。
### 8.3 取消与停止
- 取消沿用 `DualArmAction` 既有策略(含软停逻辑,按当前实现执行)。
- 硬急停状态机作为后续增强项,不在本次服务化重构内改变执行协议。
### 8.4 急停分层链路与状态机
#### 分层链路(与当前代码对齐)
```mermaid
flowchart LR
IO[外部急停IO] --> HAL[HAL安全输入]
HAL --> Driver[驱动层安全状态]
Driver --> Topic["/safety/estop_state"]
Topic --> SS[SafetyService]
SS --> DA[DualArmAction]
SS --> ASS[ArmStatusService]
DA --> Stop1["Stop1减速停车<br/>MakeStopTrajectory + cancel"]
ASS --> Status["/robot_control/arm_status<br/>estop_latched/generation"]
```
- `SafetyService` 订阅 `/safety/estop_state`,维护 `latched/generation` 原子状态。
- `SafetyService` 提供 `/robot_control/reset_estop`
- 必须已收到至少一次 `/safety/estop_state`(完成驱动侧状态同步);
- 且当前 `latched=false`(硬件链路已释放);
- 否则返回 `success=false`,拒绝复位请求。
- `DualArmAction`**接收目标前****执行循环中** 检查急停锁存:
- 锁存态下拒绝新目标;
- 执行中触发时,执行 Stop1 风格减速停车并返回中断结果(`E_STOP_STOP1`)。
- `ArmStatusService``estop_latched/estop_generation` 合并进 `RobotArmStatus`,便于上层统一观测执行态与安全态。
#### 急停状态机(软件视角)
```mermaid
stateDiagram-v2
[*] --> RUNNING
RUNNING --> E_STOP_LATCHED: estop_latched=true
E_STOP_LATCHED --> STOPPING: 执行中检测到急停
STOPPING --> ABORTED: Stop1完成/Action中止
E_STOP_LATCHED --> REJECT_NEW_GOAL: 新目标到达
REJECT_NEW_GOAL --> E_STOP_LATCHED
ABORTED --> WAIT_RESET: 等待安全链路复位
WAIT_RESET --> RUNNING: latched=false 且允许复位
```
- `generation` 用于区分“同一锁存态”与“新触发事件”,上层可据此做边沿判定与告警去重。
- `ResetEStop` 服务仅作为复位请求入口,复位成功前提是“驱动状态已同步 + 锁存已释放”,不允许绕过硬件安全链路。
---
## 9. 错误处理与恢复策略
### 9.1 错误分类
- 请求参数错误:`arm_id`/`joint_num` 越界、目标维度不匹配。
- 状态错误:未初始化完成、控制器不可用、资源冲突。
- 规划错误MoveIt 无解/规划失败。
- 执行错误FJT 执行失败、超时、被取消。
### 9.2 上报语义
- Action`result.success=false + message`
- Service`success/result + message` 明确失败原因。
- 日志:在 service/action 层保留关键错误日志,便于定位。
### 9.3 恢复策略
- 参数错误:立即失败返回,不进入执行链路。
- 规划失败:保持控制器状态不变,允许重试。
- 执行失败:由上层决定重试、回零或清错。
- 清错与使能:通过 service 明确触发,避免隐式副作用。
---
## 10. 可观测性与调试支持
- 统一状态topic`/robot_control/arm_status` 作为运维与调试主观测口。
- 建议录包:`joint_states``dynamic_joint_states``robot_control/arm_status`、Action feedback/result。
- 调试逆解:调用 `/robot_control/inverse_kinematics` 并与 FK结果做闭环比对。
---
## 11. 安全性与可靠性
### 11.1 安全性
- 关节限位在规划入口前校验。
- 使能/清错接口显式化,避免业务层绕过硬件控制语义。
- 双臂/单臂关节范围通过 `arm_id + joint_num` 严格限定。
### 11.2 可靠性
- 节点瘦身 + 服务分层后,修改影响面更小。
- 状态发布与执行链路解耦,降低互相阻塞风险。
- IK 与 FK 语义清晰,避免“规划末点冒充逆解”的歧义。
---
## 12. 测试设计
### 12.1 功能测试
- 单臂/双臂 PTP 成功路径MOVEJ
- `SetArmEnable`
- `arm_id=0/1/2`
- `joint_num=0`(全部)与 `1~6`(单关节)
- `ClearArmError`:单关节、单臂全部、双臂全部。
- `InverseKinematics`:左/右臂典型位姿、不可达位姿。
### 12.2 一致性测试
- 校验 `RobotArmStatus.joint_positions` 与实际 `joint_states` 对齐。
- 校验 `RobotArmStatus` 中 pose 是否与同帧关节值 FK 一致。
- 校验 service 调用后使能/错误缓存刷新行为。
### 12.3 稳定性测试
- 连续PTP请求压力含取消
- 状态topic长时间发布10ms 周期稳定性与CPU占用。
- 控制器重启后的恢复行为switch_controller + service/action 重新可用)。
---
## 13. 变更影响分析
- **架构层面**从“node内聚实现”升级为“service分层实现”可维护性提升。
- **兼容性**:对外 action/service/topic 名称保持不变,调用方无感。
- **性能影响**:新增服务对象几乎无额外开销;状态发布逻辑迁移不改变频率与数据结构。
- **风险点**需关注缓存一致性dynamic state 与 service写入并存场景建议通过录包回归验证。

View File

@@ -55,6 +55,7 @@ set(SOURCES
src/core/controller_factory.cpp
src/core/remote_controller.cpp
src/services/motor_service.cpp
src/services/safety_service.cpp
src/services/arm_hardware_service.cpp
src/services/kinematics_service.cpp
src/services/arm_status_service.cpp

View File

@@ -7,11 +7,13 @@
namespace robot_control
{
class RobotControlManager;
class SafetyService;
struct ActionContext
{
rclcpp::Node* node{nullptr};
RobotControlManager* robot_control_manager{nullptr};
SafetyService* safety_service{nullptr};
};
} // namespace robot_control

View File

@@ -19,13 +19,15 @@ class MoveWaistAction;
class MoveWheelAction;
class DualArmAction;
class MotorService;
class SafetyService;
class ActionManager
{
public:
ActionManager(
rclcpp::Node* node,
RobotControlManager& robot_control_manager);
RobotControlManager& robot_control_manager,
const std::shared_ptr<SafetyService>& safety_service);
~ActionManager();
@@ -35,6 +37,7 @@ private:
rclcpp::Node* node_{nullptr};
RobotControlManager& robot_control_manager_;
std::shared_ptr<MotorService> motor_service_;
std::shared_ptr<SafetyService> safety_service_;
std::unique_ptr<MoveHomeAction> move_home_;
std::unique_ptr<MoveLegAction> move_leg_;

View File

@@ -135,6 +135,8 @@ private:
const std::vector<std::string>& right_joint_names,
bool* canceled) const;
bool is_estop_latched_() const;
void save_trajectory_to_file_(
const trajectory_msgs::msg::JointTrajectory& traj,
const std::string& tag) const;

View File

@@ -25,6 +25,7 @@
#include "services/arm_hardware_service.hpp"
#include "services/kinematics_service.hpp"
#include "services/arm_status_service.hpp"
#include "services/safety_service.hpp"
#include "control_msgs/msg/dynamic_interface_group_values.hpp"
#include "control_msgs/msg/dynamic_joint_state.hpp"
@@ -87,6 +88,7 @@ namespace robot_control
std::unique_ptr<ArmHardwareService> arm_hardware_service_; ///< 机械臂GPIO服务实现
std::unique_ptr<KinematicsService> kinematics_service_; ///< 运动学服务实现
std::unique_ptr<ArmStatusService> arm_status_service_; ///< 机械臂状态服务实现
std::shared_ptr<SafetyService> safety_service_; ///< 急停状态服务实现
// ==================== 控制状态 ====================
bool is_robot_enabled_; ///< 机器人是否启用

View File

@@ -8,6 +8,7 @@
#include "control_msgs/msg/dynamic_joint_state.hpp"
#include "interfaces/msg/robot_arm_status.hpp"
#include "core/robot_control_manager.hpp"
#include "services/safety_service.hpp"
namespace robot_control
{
@@ -18,6 +19,7 @@ public:
ArmStatusService(
rclcpp::Node* node,
RobotControlManager* manager,
SafetyService* safety_service,
const rclcpp::Publisher<interfaces::msg::RobotArmStatus>::SharedPtr& status_pub,
std::vector<bool>* joint_enabled_cache,
std::vector<int32_t>* joint_error_code_cache);
@@ -28,6 +30,7 @@ public:
private:
rclcpp::Node* node_{nullptr};
RobotControlManager* manager_{nullptr};
SafetyService* safety_service_{nullptr};
rclcpp::Publisher<interfaces::msg::RobotArmStatus>::SharedPtr status_pub_;
std::vector<bool>* joint_enabled_cache_{nullptr};
std::vector<int32_t>* joint_error_code_cache_{nullptr};

View File

@@ -0,0 +1,38 @@
#pragma once
#include <atomic>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "interfaces/msg/e_stop_state.hpp"
#include "interfaces/srv/reset_e_stop.hpp"
namespace robot_control
{
class SafetyService
{
public:
using EStopState = interfaces::msg::EStopState;
using ResetEStop = interfaces::srv::ResetEStop;
explicit SafetyService(rclcpp::Node* node);
void OnEStopState(const EStopState::SharedPtr msg);
void HandleResetEStop(
const std::shared_ptr<ResetEStop::Request> request,
std::shared_ptr<ResetEStop::Response> response);
bool IsEstopLatched() const { return estop_latched_.load(); }
uint32_t GetEstopGeneration() const { return estop_generation_.load(); }
private:
rclcpp::Node* node_{nullptr};
rclcpp::Subscription<EStopState>::SharedPtr estop_sub_;
rclcpp::Service<ResetEStop>::SharedPtr reset_estop_srv_;
std::atomic<bool> estop_state_received_{false};
std::atomic<bool> estop_latched_{false};
std::atomic<uint32_t> estop_generation_{0};
};
} // namespace robot_control

View File

@@ -14,14 +14,17 @@
#include "core/robot_control_manager.hpp"
#include "services/motor_service.hpp"
#include "services/safety_service.hpp"
namespace robot_control
{
ActionManager::ActionManager(
rclcpp::Node* node,
RobotControlManager& robot_control_manager)
RobotControlManager& robot_control_manager,
const std::shared_ptr<SafetyService>& safety_service)
: node_(node)
, robot_control_manager_(robot_control_manager)
, safety_service_(safety_service)
{
motor_service_ = std::make_shared<MotorService>(node_);
}
@@ -33,6 +36,7 @@ void ActionManager::initialize()
ActionContext ctx;
ctx.node = node_;
ctx.robot_control_manager = &robot_control_manager_;
ctx.safety_service = safety_service_.get();
move_home_ = std::make_unique<MoveHomeAction>(ctx);
move_leg_ = std::make_unique<MoveLegAction>(ctx);

View File

@@ -1,6 +1,7 @@
#include "actions/dual_arm_action.hpp"
#include "core/robot_control_manager.hpp"
#include "core/common_enum.hpp"
#include "services/safety_service.hpp"
#include "utils/future_utils.hpp"
#include "utils/trajectory_utils.hpp"
#include "utils/velocity_scale_utils.hpp"
@@ -59,6 +60,12 @@ rclcpp_action::GoalResponse DualArmAction::handle_goal_(
"Received DualArm goal: velocity_scaling=%d, arm_motion_params_count=%zu",
goal->velocity_scaling, goal->arm_motion_params.size());
if (is_estop_latched_()) {
RCLCPP_ERROR(ctx_.node->get_logger(),
"DualArm goal rejected: E-Stop latched");
return rclcpp_action::GoalResponse::REJECT;
}
// 关节状态未稳定前进行规划/执行,容易出现起点跳变(看起来像启停抖动)
if (!ctx_.robot_control_manager->RobotInitFinished()) {
RCLCPP_ERROR(ctx_.node->get_logger(),
@@ -439,6 +446,12 @@ void DualArmAction::execute_single_arm_(
{
const auto& arm_param = goal->arm_motion_params[0];
const uint8_t arm_id = arm_param.arm_id;
if (is_estop_latched_()) {
result->success = false;
result->message = "E_STOP_LATCHED";
goal_handle->abort(result);
return;
}
if (arm_id != ArmMotionParams::ARM_LEFT && arm_id != ArmMotionParams::ARM_RIGHT) {
result->success = false;
result->message = "Invalid arm_id for single-arm goal";
@@ -579,6 +592,12 @@ void DualArmAction::execute_dual_arm_(
double acceleration_scaling)
{
const size_t ARM_DOF = motion_params.arm_dof_;
if (is_estop_latched_()) {
result->success = false;
result->message = "E_STOP_LATCHED";
goal_handle->abort(result);
return;
}
if (!left_fjt_client_ || !right_fjt_client_) {
result->success = false;
@@ -862,6 +881,16 @@ bool DualArmAction::wait_for_execution_(
auto start_time = ctx_.node->now();
while (rclcpp::ok()) {
if (is_estop_latched_()) {
if (cancel_actions) {
cancel_actions();
}
result->success = false;
result->message = "E_STOP_STOP1";
goal_handle->abort(result);
return false;
}
if (goal_handle->is_canceling()) {
if (cancel_actions) {
cancel_actions();
@@ -896,6 +925,11 @@ bool DualArmAction::wait_for_execution_(
return rclcpp::ok();
}
bool DualArmAction::is_estop_latched_() const
{
return ctx_.safety_service && ctx_.safety_service->IsEstopLatched();
}
void DualArmAction::execute_(const std::shared_ptr<GoalHandleDualArm> goal_handle)
{
if (!ctx_.robot_control_manager) {

View File

@@ -38,9 +38,11 @@ RobotControlNode::RobotControlNode() : Node("robot_control_node")
kinematics_service_ = std::make_unique<KinematicsService>(
this,
&robotControlManager_);
safety_service_ = std::make_shared<SafetyService>(this);
arm_status_service_ = std::make_unique<ArmStatusService>(
this,
&robotControlManager_,
safety_service_.get(),
robot_arm_status_pub_,
&joint_enabled_cache_,
&joint_error_code_cache_);
@@ -83,7 +85,8 @@ RobotControlNode::RobotControlNode() : Node("robot_control_node")
// 初始化 ActionManager内部会创建所需的 MotorService
action_manager_ = std::make_unique<ActionManager>(
this,
robotControlManager_);
robotControlManager_,
safety_service_);
action_manager_->initialize();
const bool ret1 = activateController("left_arm_controller");

View File

@@ -8,11 +8,13 @@ namespace robot_control
ArmStatusService::ArmStatusService(
rclcpp::Node* node,
RobotControlManager* manager,
SafetyService* safety_service,
const rclcpp::Publisher<interfaces::msg::RobotArmStatus>::SharedPtr& status_pub,
std::vector<bool>* joint_enabled_cache,
std::vector<int32_t>* joint_error_code_cache)
: node_(node),
manager_(manager),
safety_service_(safety_service),
status_pub_(status_pub),
joint_enabled_cache_(joint_enabled_cache),
joint_error_code_cache_(joint_error_code_cache)
@@ -96,6 +98,14 @@ void ArmStatusService::PublishRobotArmStatus()
msg.joint_error_codes.assign(msg.joint_names.size(), 0);
}
if (safety_service_) {
msg.estop_latched = safety_service_->IsEstopLatched();
msg.estop_generation = safety_service_->GetEstopGeneration();
} else {
msg.estop_latched = false;
msg.estop_generation = 0;
}
(void)manager_->GetArmCurrentPose(0, &msg.left_arm_pose, nullptr);
(void)manager_->GetArmCurrentPose(1, &msg.right_arm_pose, nullptr);

View File

@@ -0,0 +1,77 @@
#include "services/safety_service.hpp"
#include <functional>
namespace robot_control
{
SafetyService::SafetyService(rclcpp::Node* node)
: node_(node)
{
if (node_) {
estop_sub_ = node_->create_subscription<EStopState>(
"/safety/estop_state",
10,
std::bind(&SafetyService::OnEStopState, this, std::placeholders::_1));
reset_estop_srv_ = node_->create_service<ResetEStop>(
"/robot_control/reset_estop",
std::bind(
&SafetyService::HandleResetEStop,
this,
std::placeholders::_1,
std::placeholders::_2));
}
}
void SafetyService::OnEStopState(const EStopState::SharedPtr msg)
{
if (!msg) {
return;
}
const bool prev = estop_latched_.load();
estop_state_received_.store(true);
estop_latched_.store(msg->latched);
estop_generation_.store(msg->generation);
if (node_ && msg->latched && !prev) {
RCLCPP_ERROR(
node_->get_logger(),
"E-Stop latched: source=%u generation=%u reason='%s'",
static_cast<unsigned>(msg->source),
static_cast<unsigned>(msg->generation),
msg->reason.c_str());
}
}
void SafetyService::HandleResetEStop(
const std::shared_ptr<ResetEStop::Request> request,
std::shared_ptr<ResetEStop::Response> response)
{
if (!response) {
return;
}
if (!request || !request->reset) {
response->success = false;
response->message = "Invalid reset request";
return;
}
if (!estop_state_received_.load()) {
response->success = false;
response->message =
"No /safety/estop_state received yet, reset is not allowed before safety state sync";
return;
}
if (estop_latched_.load()) {
response->success = false;
response->message =
"E-Stop is still latched by safety input, wait for /safety/estop_state release";
return;
}
response->success = true;
response->message = "E-Stop reset request accepted";
}
} // namespace robot_control