做机器人链路开发,大概率都有过这样的经历:实验室里正常跑通的流程,一到现场就出各种幺蛾子------相机突然掉线、点云为空、算法超时、机器人位姿获取失败......这些异常不会每天发生,但一旦出现,轻则流程卡死,重则机械臂误运动、撞工件,甚至危及现场人员安全。更令人头疼的是,这类现场异常往往难以复现,排查起来耗时费力,甚至可能导致项目延期、成本超支。
其实,正常流程跑通只是机器人开发的第一步,真正决定系统工程质量的,是对异常流程的把控。而故障注入,就是我们提前"踩坑"、验证系统可靠性的核心工程方法,它能帮我们在实验室环境中模拟现场可能出现的各类异常,提前发现隐患、优化系统,避免问题流入现场。
尤其是在AI时代,借助强大的编码AI工具可以快速搭建故障注入模块,大大降低试错的时间成本
故障注入不是抽象的测试概念,而是一套可落地的实践:在可控、安全、可复现的条件下,主动制造异常,验证机器人系统在异常情况下是否还能安全、稳定、可恢复。无论是工业机械臂、移动机器人,还是服务机器人,只要涉及运动控制和环境交互,故障注入都是不可或缺的测试环节。
一、故障注入到底是什么?
故障注入(Fault Injection) ,简单说就是人为制造故障,用来测试系统面对异常时的行为。它的核心不是"搞破坏",而是验证系统的"抗造能力"------毕竟,机器人系统在真实场景中,必然会面临硬件老化、网络波动、数据异常等问题,提前通过故障注入验证系统的应对能力,才能避免现场事故的发生。具体来说,故障注入需要回答这几个核心问题:
-
系统是否能检测到故障?(比如相机掉线后,系统能否快速识别并上报)
-
系统是否能拒绝非法数据?(比如算法返回NaN目标点,系统能否拦截并拒绝执行)
-
系统是否能超时退出,不陷入死等?(比如算法超时后,控制节点能否及时退出等待,避免卡死)
-
系统是否能进入安全状态,避免误动作?(比如故障发生后,机械臂能否停止运动,进入安全待机状态)
-
故障解除后,系统是否能恢复正常?(比如相机重新连接后,系统能否自动重启流程,无需人工干预)
-
是否留下足够日志,方便定位问题?(比如故障发生时,能否记录故障类型、发生时间、当前系统状态等关键信息)
故障注入 = 故意让系统坏一部分,观察整体会不会失控 。它的本质是"主动试错",通过模拟各类极端场景,倒逼我们完善系统的异常处理逻辑,提升系统的鲁棒性。
二、故障注入在机器人系统中的作用
普通后端系统出错,最多是接口失败、页面报错,影响的是"数据层面 ",修复起来也相对简单;但机器人系统出错,影响的是"物理层面"------软件错误会通过运动控制链路,直接转化为机械臂的真实动作,其后果远比后端系统出错严重。
比如这些场景,每一个都可能引发安全事故或设备损坏:
-
算法返回NaN目标点,机器人误运动撞工件,导致工件报废、机械臂损坏;
-
TF坐标查不到,机械臂凭"感觉"运动,可能碰撞周围设备或现场人员;
-
控制线程卡死,机器人无法停止,持续执行错误动作,引发安全隐患;
-
网络抖动导致旧数据被执行,比如机器人已经移动位置,但仍使用之前的位姿数据,引发误操作;
所以机器人开发,不能只问"程序有没有跑起来",更要问"异常情况下,机器人会不会乱动"。而故障注入,就是提前模拟这些风险,把问题解决在实验室里------相比于现场排查故障的高昂成本,在实验室通过故障注入发现并修复问题,性价比要高得多。这也是为什么,工业机器人、自动驾驶机器人等对安全性要求高的领域,故障注入已经成为标配测试手段。
三、机器人链路中故障注入的位置
一个典型的ROS2机器人链路,从传感器到真实机器人,每一层都可能出现故障,故障注入可以覆盖全链路,做到"层层设防"。不同层级的故障,其影响范围和风险等级不同,注入方式也有所差异。
text
┌──────────────┐
│ 传感器硬件 │ 2D相机 / 3D相机 / 激光 / 编码器
└──────┬───────┘
│
v
┌──────────────┐
│ 厂家 SDK 层 │ 海康 SDK / Basler SDK / 雷达 SDK
└──────┬───────┘
│
v
┌──────────────┐
│ ROS2 Driver │ Image / PointCloud2 / CameraInfo
└──────┬───────┘
│
v
┌──────────────┐
│ 算法节点 │ 识别 / 定位 / 分割 / 焊缝提取
└──────┬───────┘
│
v
┌──────────────┐
│ 控制编排节点 │ 状态机 / 流程控制 / 运动决策
└──────┬───────┘
│
v
┌──────────────┐
│ 机器人驱动 │ FANUC / UR / ABB / 自研控制器
└──────┬───────┘
│
v
┌──────────────┐
│ 真实机器人 │ 机械臂 / 末端执行器 / 焊枪
└──────────────┘
从传感器的"无图、空点云",到SDK的"初始化失败、取流超时",再到ROS2通信的"Topic断流、QoS不匹配"、算法的"超时、返回非法数据",甚至机器人驱动的"运动指令失败、连接断开",每一层都值得我们注入故障,验证系统的应对能力。比如传感器层的故障多为硬件相关,注入方式相对简单;而控制层的故障直接影响机器人运动,注入时需要格外注意安全边界。
四、避坑:故障注入不是"随便拔线"
很多人第一次接触故障注入,会觉得就是"拔网线、断电、杀进程"------这些确实是故障注入的一部分,属于"粗暴式故障注入 ",但在工程实践中,更重要的是"可控、可复现、可观测、可恢复 "的"精细化故障注入"。粗暴式故障注入虽然简单,但难以复现、无法精准控制故障场景,也无法观测系统的完整响应过程,往往达不到测试目的。
一个合格的故障注入测试,必须明确这8个问题,确保测试的有效性和安全性:
-
注入什么故障?(明确故障类型,比如"点云为空""算法超时",而非笼统的"设备故障")
-
注入在哪一层?(明确故障发生的链路层级,比如传感器层、ROS2通信层,便于定位问题)
-
什么时候注入?(明确故障注入的时机,比如流程启动时、相机采集后,模拟真实场景)
-
持续多久?(明确故障持续时间,比如超时故障持续10秒,模拟短暂异常)
-
期望系统如何响应?(提前定义合理的响应行为,比如"超时后进入安全状态",作为测试通过的标准)
-
如何判断测试通过?(明确判断依据,比如"是否记录错误日志""是否停止运动")
-
如何恢复正常状态?(明确故障解除后的恢复流程,比如"重启节点""重新采集数据")
-
是否存在设备安全风险?(评估故障注入可能带来的安全隐患,提前做好防护措施)
简单说,故障注入是"有设计的异常测试",不是"粗暴破坏"。比如我们要测试"算法超时",不是直接杀算法进程,而是让算法节点故意sleep指定时间(比如12秒),模拟超时场景,同时观察控制节点是否能在设定的超时时间(比如10秒)内退出等待、进入安全状态,并记录完整的错误日志。这样的测试的才能精准验证系统的超时处理逻辑,也便于后续复盘和优化。
五、区分 故障、错误、失效(3个易混概念)
做故障注入前,必须分清这三个概念------它们是可靠性工程的基础,也是我们设计测试用例、定位故障根源的核心依据。很多开发者在排查问题时,容易将这三个概念混淆,导致定位方向出错,效率低下。
| 概念 | 英文 | 含义 | 例子 |
|---|---|---|---|
| 故障 | Fault | 问题的根因,是系统本身存在的缺陷或外部环境导致的异常根源 | 相机网线接触不良、传感器硬件老化、网络线路损坏 |
| 错误 | Error | 系统内部出现的错误状态,是故障的直接表现 | 图像超时、点云为空、SDK返回错误码、TF查询失败 |
| 失效 | Failure | 对外表现出的功能失败,是错误未被及时处理导致的最终结果 | 机器人无法继续定位、机械臂误运动、流程卡死无法恢复 |
举个完整例子 :
相机网线松动(Fault,根因)→ ROS2 Driver收不到图(Error,内部错误)→ 机器人流程卡死(Failure,功能失效) 。
从这个例子可以看出,故障是"因",错误是"果",而失效是"严重的果"。故障注入的核心,就是主动制造Fault或Error,验证系统是否能通过异常处理逻辑,阻止Error升级为Failure,确保系统的安全性和可靠性。
比如我们注入"点云为空"(Error),就是为了验证系统能否及时拦截,避免出现"机器人误运动"(Failure)。
六、C++ ROS2 机器人常见故障类型
结合ROS2机器人的链路特点,我整理了6个核心层的常见故障,每个都附注入方式 和期望行为,直接就能落地测试。需要注意的是,不同类型的故障,其注入工具和测试重点不同,建议结合项目实际场景,针对性设计测试用例。
6.1 传感器层故障(最常见)
传感器是机器人的"眼睛",负责采集环境和自身状态数据,是异常的重灾区------无论是硬件本身的故障,还是环境干扰导致的数据异常,都可能影响整个链路的正常运行。重点测试这些场景:
| 故障 | 注入方式 | 期望行为 |
|---|---|---|
| 相机无图 | 停止发布Image消息;或通过ROS2工具(如ros2 topic pub)发布空的Image消息 | 下游节点(如算法节点)检测到消息断流,触发超时逻辑,不继续执行后续运动流程,记录错误日志 |
| 点云为空 | 发布空的PointCloud2消息(width和height均为0);或修改点云数据,将所有点的坐标设为空 | 算法节点拒绝处理空点云,返回错误状态,控制节点收到错误后,进入等待或重试状态,记录错误日志 |
| 点云含NaN | 注入非法点(x/y/z为NaN或Inf);可通过修改ROS2 Driver代码,在发布点云前插入非法数据 | 算法节点过滤非法点,或直接终止本轮处理,返回无效结果,控制节点拦截无效结果,不发送运动指令 |
| 时间戳异常 | 修改header.stamp跳变(比如设置为未来10秒或过去10秒);或频繁修改时间戳,模拟时间同步异常 | 下游节点检测到时间戳异常,拒绝使用该数据,触发数据有效性校验失败逻辑,记录时间同步异常日志 |
控制侧必须加校验逻辑,避免非法数据流入运动控制环节,比如点云判空逻辑(可直接嵌入ROS2 Driver或算法节点的回调函数中):
cpp
if (cloud->width * cloud->height == 0) {
RCLCPP_ERROR(logger_, "Received empty point cloud");
return false; // 终止后续处理,避免无效数据流入
}
6.2 ROS2 通信层故障
ROS2的Topic/Service/Action通信,是机器人链路的"神经",负责各节点间的数据传输和指令交互。ROS2基于DDS协议,虽然可靠性比ROS1有提升,但在复杂现场环境中,仍可能出现通信异常,常见故障及注入方式如下:
-
Topic断流:停止发布某个Topic(比如相机的Image Topic);或通过ros2 topic hz命令查看 Topic 频率,人为降低发布频率至0
-
Service超时:让Service Server故意sleep(比如sleep 15秒),超过调用方设置的超时时间(比如10秒);或修改Service Server代码,延迟返回结果
-
消息延迟/乱序:通过Linux网络工具(如tc)增加网络延迟和丢包;或修改发布节点代码,打乱消息发布顺序
-
节点异常退出:直接杀掉某个节点(如算法节点、控制节点);或通过代码模拟节点崩溃(比如主动抛出异常)
期望行为 :节点间通信异常时,系统应做到"不死等、不使用旧结果、不发送运动指令 ",状态机及时进入错误状态,日志明确记录异常类型(如"Topic断流""Service超时"),便于后续排查。例如,控制节点调用算法Service超时后,应立即退出等待,进入安全状态,而不是一直阻塞在等待逻辑中。
6.3 TF 坐标变换故障
TF(Transform Frame)是机器人定位的核心,负责将不同坐标系下的数据(如相机坐标系、机器人基坐标系、工具坐标系)进行转换,一旦TF出现异常,很容易导致机械臂误运动------这是很多机器人开发中容易忽略的故障点,也是引发安全事故的重要原因之一。重点测试以下场景:
-
TF查不到:关闭static_transform_publisher节点(负责发布静态TF,如相机到基坐标系的变换);或修改TF发布节点,停止发布指定的TF变换
-
TF过期:发布旧时间戳的TF消息(比如时间戳比当前系统时间早10秒);或降低TF发布频率,导致TF数据过期
-
frame_id错误:修改消息的frame_id为不存在的值(比如将"camera_link"改为"unknown_link");或修改TF发布节点的frame_id,导致坐标系不匹配
防护逻辑示例(TF查询加超时,避免死等,可嵌入控制节点或算法节点中):
cpp
try {
auto transform = tf_buffer_->lookupTransform(
"base_link", // 目标坐标系
msg->header.frame_id, // 源坐标系(来自消息)
msg->header.stamp, // 时间戳(与消息同步)
rclcpp::Duration::from_seconds(0.2) // 超时时间(200ms)
);
} catch (const tf2::TransformException& ex) {
RCLCPP_ERROR(logger_, "TF lookup failed: %s", ex.what());
return false; // 终止后续运动,避免误动作
}
关键原则:TF查不到时,绝对不能凭感觉继续运动。很多开发者为了"避免流程卡死",会在TF查询失败时,使用默认的变换矩阵,这种做法非常危险,很可能导致机械臂误运动,必须严格禁止。
6.4 算法接口故障(风险高)
算法返回的结果(目标点、位姿、置信度、焊缝中心线等),直接决定机器人的运动轨迹和动作,一旦算法接口出现故障,很容易引发安全事故。因此,控制侧必须对算法返回结果进行严格校验,常见故障如下:
-
算法无响应、超时:算法节点收到请求后,不返回任何结果;或故意延迟返回(比如sleep 15秒)
-
返回空结果、NaN、极大值:算法返回空的目标点列表;或返回的坐标为NaN、Inf;或返回的坐标超出机器人工作空间(如z轴坐标为10米)
-
返回旧数据(时间戳过期):算法返回的结果时间戳,比当前系统时间早很多(比如5秒),属于过期数据
-
置信度过低:算法返回的结果置信度低于设定阈值(比如0.6),结果可靠性不足
控制侧二次校验逻辑示例(可封装为独立函数,在使用算法结果前调用):
cpp
bool isValidTarget(const Target& target)
{
// 校验坐标是否合法(非NaN/Inf,避免非法数据)
if (!std::isfinite(target.x) || !std::isfinite(target.y) || !std::isfinite(target.z)) {
RCLCPP_ERROR(logger_, "Target pose contains NaN or Inf");
return false;
}
// 校验置信度(确保结果可靠性)
if (target.confidence < 0.6) {
RCLCPP_WARN(logger_, "Target confidence too low: %.2f", target.confidence);
return false;
}
// 校验坐标范围(确保在机器人工作空间内)
if (target.z < 0.0 || target.z > 2.0) {
RCLCPP_ERROR(logger_, "Target z coordinate out of range: %.2f", target.z);
return false;
}
// 校验时间戳(确保数据新鲜)
auto now = rclcpp::Clock(RCL_STEADY_TIME).now();
auto age = (now - target.stamp).seconds();
if (age > 0.5) { // 数据超过500ms视为过期
RCLCPP_ERROR(logger_, "Target data is outdated: %.2fs old", age);
return false;
}
return true;
}
工程原则:算法返回值不能被默认信任,运动控制前必须做二次校验。算法本身可能存在精度不足、异常处理不完善等问题,控制侧作为"最后一道防线",必须通过严格的校验逻辑,拦截非法数据,避免引发安全事故。
七、故障注入的4层方法
故障注入不用一步到位,可按分层逐步落地,适合不同阶段的项目需求------从简单的数据级注入,到复杂的系统级注入,逐步提升系统的可靠性。不同层级的注入方法,其实现成本、安全风险、测试效果都有所差异,可根据项目进度和测试需求选择。
7.1 第一层:数据级故障注入(最简单,适合自动化)
直接修改发布的消息数据,模拟异常场景,是最基础、最安全的故障注入方式,无需修改业务代码,适合自动化测试和CI/CD集成 。这种方式的核心是"修改数据,不影响硬件和节点运行",优点是安全、可控、不依赖真实硬件,适合在仿真环境或离线环境中频繁测试。
示例:通过ROS2工具(ros2 topic pub)发布空PointCloud2消息,测试下游算法和控制节点的处理逻辑;或通过修改ROS2 Driver代码,在发布数据前插入非法数据(如NaN、空数据)。此外,也可以使用ros2 bag工具,录制异常数据,后续反复回放,实现故障复现。
7.2 第二层:接口级故障注入(适合C++工程)
通过接口抽象,实现"正常实现"和"故障实现"的切换,不污染业务代码,是C++ ROS2工程中最推荐的故障注入方式之一。这种方式的核心是"依赖注入",通过定义抽象接口,将业务逻辑与外部依赖(如机器人驱动、SDK)解耦,故障注入时,只需替换接口的实现即可。
比如定义机器人驱动接口,故障注入时使用故障实现,正常运行时使用真实实现,无需修改业务代码:
cpp
// 接口定义(抽象出机器人驱动的核心功能)
class IRobotDriver {
public:
virtual ~IRobotDriver() = default;
// 获取当前TCP位姿
virtual std::optional<Pose> getCurrentPose() = 0;
// 运动到目标位姿
virtual Result moveTo(const Pose& pose) = 0;
// 检查机器人连接状态
virtual bool isConnected() const = 0;
};
// 正常实现(调用真实机器人驱动接口,如FANUC、UR)
class FanucRobotDriver : public IRobotDriver {
public:
std::optional<Pose> getCurrentPose() override {
// 调用FANUC SDK接口,获取真实位姿
Pose pose;
if (fanuc_sdk_->get_tcp_pose(pose)) {
return pose;
}
return std::nullopt;
}
Result moveTo(const Pose& pose) override {
// 调用FANUC SDK接口,发送运动指令
auto ret = fanuc_sdk_->move_to_pose(pose);
return Result{ret == 0, ret, fanuc_sdk_->get_error_msg(ret)};
}
bool isConnected() const override {
return fanuc_sdk_->is_connected();
}
private:
std::shared_ptr<FanucSDK> fanuc_sdk_;
};
// 故障实现(注入位姿获取失败、运动失败等故障)
class FaultyRobotDriver : public IRobotDriver {
public:
// 注入:获取位姿失败
std::optional<Pose> getCurrentPose() override {
RCLCPP_WARN(logger_, "Injected fault: get current pose failed");
return std::nullopt; // 故意返回空
}
// 注入:运动指令失败
Result moveTo(const Pose& pose) override {
RCLCPP_WARN(logger_, "Injected fault: move to pose failed");
return Result{false, 5001, "Injected move failure"};
}
// 注入:机器人断连
bool isConnected() const override {
RCLCPP_WARN(logger_, "Injected fault: robot disconnected");
return false;
}
};
7.3 第三层:节点级故障注入(模拟节点异常)
写一个"假节点"(如fake algorithm node、fake camera node),支持多种故障模式,模拟真实节点的异常行为,测试控制节点对算法、传感器等节点异常的处理能力。这种方式的核心是"模拟节点",无需修改真实节点代码,可灵活切换故障模式,适合测试控制节点的状态机逻辑。
例如,实现一个fake algorithm node,支持以下故障模式:
-
正常模式:收到点云消息后,正常返回目标点;
-
超时模式:收到请求后,sleep指定时间(如15秒),模拟算法超时;
-
空结果模式:返回空的目标点列表,模拟算法未检测到目标;
-
NaN模式:返回的目标点坐标为NaN,模拟算法数据异常;
-
错误坐标系模式:返回的目标点frame_id为不存在的值,模拟坐标系错误。
通过参数服务器(Parameter Server)动态切换故障模式,无需重启节点,测试效率更高。
7.4 第四层:系统级故障注入
模拟现场真实故障,直接操作硬件、网络或系统资源,测试系统在极端场景下的鲁棒性。这种方式最接近真实现场,测试效果最好,但安全风险也最高,需要在受控环境中进行,避免设备损坏或安全事故。
常见的系统级故障注入方式包括:
-
网络故障:断开网络、增加网络延迟/丢包、限制网络带宽;
-
硬件故障:拔掉相机网线、断电重启相机、断开机器人连接;
-
系统资源故障:限制CPU使用率、限制内存使用、磁盘满额;
-
节点故障:随机杀掉节点、重启节点、模拟节点崩溃。
Linux网络故障注入示例(增加延迟和丢包,适合测试网络抖动对ROS2通信的影响):
bash
# 给eth0网卡增加100ms延迟、5%丢包(模拟现场网络抖动)
sudo tc qdisc add dev eth0 root netem delay 100ms loss 5%
# 恢复网络正常状态
sudo tc qdisc del dev eth0 root
这种方式适合测试:网络抖动下图像是否超时、机器人控制链路是否稳定、心跳检测是否有效、断线后能否自动恢复等场景。测试时,建议先在仿真环境或无真实运动的场景中验证,再逐步迁移到实机测试。
八、工程实践:设计故障注入系统
在实际项目中,不建议在业务代码中到处写"if (inject_fault_)"这样的判断逻辑------这种方式会污染业务代码,难以维护,且无法灵活切换故障模式。推荐做一个独立的FaultInjector组件,集中管理故障注入,实现"故障开关集中控制、故障类型可配置、不污染业务代码"。
8.1 实现一个简单的FaultInjector
FaultInjector组件负责读取配置、管理故障状态,提供简单的接口供业务代码调用,支持动态开关故障、切换故障类型、配置故障参数(如延迟时间)。以下是C++ ROS2版本的简单实现:
cpp
// 故障类型枚举(覆盖常见故障场景)
enum class FaultType {
None, // 无故障
EmptyPointCloud, // 空点云
NanTarget, // NaN目标点
AlgorithmTimeout, // 算法超时
RobotPoseFailure, // 机器人位姿获取失败
TfFailure, // TF查询失败
TopicDisconnect // Topic断流
};
class FaultInjector {
public:
// 构造函数,从参数服务器读取配置
explicit FaultInjector(rclcpp::Node* node) {
// 故障注入总开关(默认关闭)
enabled_ = node->declare_parameter<bool>("fault_injection.enabled", false);
// 故障类型(默认无故障)
std::string type_str = node->declare_parameter<std::string>("fault_injection.type", "none");
// 故障延迟时间(单位:ms,默认0)
delay_ms_ = node->declare_parameter<int>("fault_injection.delay_ms", 0);
// 将字符串类型的故障类型转换为枚举
type_ = stringToFaultType(type_str);
// 注册参数回调,支持动态修改故障配置(无需重启节点)
param_callback_ = node->add_on_set_parameters_callback(
std::bind(&FaultInjector::onParameterChange, this, std::placeholders::_1)
);
}
// 判断故障注入是否启用
bool enabled() const { return enabled_; }
// 判断当前是否注入指定类型的故障
bool is(FaultType type) const {
return enabled_ && type_ == type;
}
// 重载,支持字符串类型的故障判断(方便业务代码调用)
bool is(const std::string& type_str) const {
return is(stringToFaultType(type_str));
}
// 获取故障延迟时间(单位:ms)
int delayMs() const { return delay_ms_; }
private:
// 将字符串转换为FaultType枚举
FaultType stringToFaultType(const std::string& type_str) {
if (type_str == "empty_point_cloud") return FaultType::EmptyPointCloud;
if (type_str == "nan_target") return FaultType::NanTarget;
if (type_str == "algorithm_timeout") return FaultType::AlgorithmTimeout;
if (type_str == "robot_pose_failure") return FaultType::RobotPoseFailure;
if (type_str == "tf_failure") return FaultType::TfFailure;
if (type_str == "topic_disconnect") return FaultType::TopicDisconnect;
return FaultType::None;
}
// 参数变化回调函数,支持动态修改故障配置
rcl_interfaces::msg::SetParametersResult onParameterChange(const std::vector<rclcpp::Parameter>& params) {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
for (const auto& param : params) {
if (param.get_name() == "fault_injection.enabled") {
enabled_ = param.as_bool();
} else if (param.get_name() == "fault_injection.type") {
type_ = stringToFaultType(param.as_string());
} else if (param.get_name() == "fault_injection.delay_ms") {
delay_ms_ = param.as_int();
}
}
return result;
}
private:
bool enabled_{false}; // 故障注入总开关
FaultType type_{FaultType::None}; // 当前故障类型
int delay_ms_{0}; // 故障延迟时间(ms)
rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr param_callback_; // 参数回调句柄
};
8.2 配置文件(yaml)
通过yaml配置文件,设置故障注入参数,支持动态修改(无需重启节点),方便测试不同故障场景:
yaml
# 故障注入配置
fault_injection:
enabled: true # 故障注入总开关(true=开启,false=关闭)
type: algorithm_timeout # 故障类型(对应FaultType枚举的字符串形式)
delay_ms: 12000 # 故障延迟时间(12秒,适合模拟算法超时)
8.3 业务代码中使用
在业务代码(如算法节点、控制节点)中,通过FaultInjector的接口判断是否注入故障,无需修改核心业务逻辑,代码侵入性低:
cpp
// 在控制节点中初始化FaultInjector
auto fault_injector = std::make_shared<FaultInjector>(this);
// 调用算法Service时,检查是否注入算法超时故障
if (fault_injector->is("algorithm_timeout")) {
RCLCPP_WARN(logger_, "Injected fault: algorithm timeout");
// 模拟算法超时,sleep指定时间
std::this_thread::sleep_for(std::chrono::milliseconds(fault_injector->delayMs()));
}
// 发布点云时,检查是否注入空点云故障
if (fault_injector->is("empty_point_cloud")) {
RCLCPP_WARN(logger_, "Injected fault: empty point cloud");
auto cloud = std::make_shared<sensor_msgs::msg::PointCloud2>();
cloud->width = 0;
cloud->height = 0;
point_cloud_pub_->publish(*cloud);
} else {
// 发布正常点云
point_cloud_pub_->publish(*cloud);
}
这样做的好处:故障开关集中管理、故障类型可配置、便于自动化测试,不会污染大量业务逻辑,后续维护和扩展也更方便。比如需要新增一种故障类型,只需修改FaultInjector的枚举和配置文件,无需修改业务代码。
九、故障注入的核心目标:验证异常
很多机器人系统,在正常流程下能稳定运行,但一旦遇到异常,就会暴露大量隐患------比如死循环等待、忙轮询占满CPU、没有timeout逻辑、使用旧数据、空指针崩溃、数组越界、NaN进入运动控制、状态机无法回到IDLE状态、日志只有一句"failed"、机器人没有进入安全状态等。这些隐患,在正常测试中很难发现,只有通过故障注入,才能逐一验证和修复。
故障注入的核心目标,就是验证这些异常路径是否可控,确保系统在遇到异常时,能做出正确的响应,避免出现安全事故或功能失效。重点检查这8个能力,覆盖异常处理的全流程:
| 检查点 | 说明 |
|---|---|
| 检测能力 | 系统能否快速发现异常(如消息断流、数据非法、节点崩溃),并上报异常状态 |
| 拦截能力 | 非法数据(如NaN、空数据、过期数据)是否被及时拦截,不流入后续运动控制环节 |
| 超时能力 | 所有等待操作(如Service调用、TF查询、数据接收)是否有timeout逻辑,能从等待中及时退出 |
| 安全能力 | 故障发生后,机器人能否及时进入safe stop状态,停止运动,避免误动作 |
| 恢复能力 | 故障解除后,系统能否自动恢复正常运行(如重新连接节点、重新采集数据),无需人工干预 |
| 日志能力 | 是否记录完整的故障日志(故障类型、发生时间、当前状态、输入数据),方便后续定位问题 |
| 观测能力 | 是否有可观测的状态标识(如Topic发布状态、节点状态码、diagnostics消息),便于实时监控 |