Skip to content

Commit 31a7540

Browse files
author
Claudio Chies
committed
feat(failure_injection): enhance failure injection with RC switch support and instance bitmasking
feat(failure_injection): add disabled failure injection manager and system command support for v5x and v6x boards
1 parent 3c07155 commit 31a7540

14 files changed

Lines changed: 269 additions & 24 deletions

File tree

ROMFS/px4fmu_common/init.d/rcS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,14 @@ else
714714
unset RC_VTXTABLE
715715
fi
716716

717+
#
718+
# Start the failure injection manager. SYS_FAILURE_EN only exists when the module is built
719+
#
720+
if param compare -s SYS_FAILURE_EN 1
721+
then
722+
failure_injection_manager start
723+
fi
724+
717725
#
718726
# Set additional parameters and env variables for selected AUTOSTART.
719727
#

boards/auterion/fmu-v6x/default.px4board

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ CONFIG_MODULES_DATAMAN=y
4848
CONFIG_MODULES_EKF2=y
4949
CONFIG_MODULES_ESC_BATTERY=y
5050
CONFIG_MODULES_EVENTS=y
51+
CONFIG_MODULES_FAILURE_INJECTION_MANAGER=n
5152
CONFIG_MODULES_FLIGHT_MODE_MANAGER=y
5253
CONFIG_MODULES_FW_ATT_CONTROL=y
5354
CONFIG_MODULES_FW_AUTOTUNE_ATTITUDE_CONTROL=y
@@ -82,6 +83,7 @@ CONFIG_MODULES_VTOL_ATT_CONTROL=y
8283
CONFIG_SYSTEMCMDS_ACTUATOR_TEST=y
8384
CONFIG_SYSTEMCMDS_BSONDUMP=y
8485
CONFIG_SYSTEMCMDS_DMESG=y
86+
CONFIG_SYSTEMCMDS_FAILURE=n
8587
CONFIG_SYSTEMCMDS_HARDFAULT_LOG=y
8688
CONFIG_SYSTEMCMDS_I2CDETECT=y
8789
CONFIG_SYSTEMCMDS_LED_CONTROL=y

boards/px4/fmu-v5x/default.px4board

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ CONFIG_MODULES_DATAMAN=y
5757
CONFIG_MODULES_EKF2=y
5858
CONFIG_MODULES_ESC_BATTERY=y
5959
CONFIG_MODULES_EVENTS=y
60+
CONFIG_MODULES_FAILURE_INJECTION_MANAGER=n
6061
CONFIG_MODULES_FLIGHT_MODE_MANAGER=y
6162
CONFIG_MODULES_FW_ATT_CONTROL=y
6263
CONFIG_MODULES_FW_AUTOTUNE_ATTITUDE_CONTROL=y
@@ -90,6 +91,7 @@ CONFIG_SYSTEMCMDS_ACTUATOR_TEST=y
9091
CONFIG_SYSTEMCMDS_BL_UPDATE=y
9192
CONFIG_SYSTEMCMDS_BSONDUMP=y
9293
CONFIG_SYSTEMCMDS_DMESG=y
94+
CONFIG_SYSTEMCMDS_FAILURE=n
9395
CONFIG_SYSTEMCMDS_GPIO=y
9496
CONFIG_SYSTEMCMDS_HARDFAULT_LOG=y
9597
CONFIG_SYSTEMCMDS_I2CDETECT=y

docs/en/debug/failure_injection.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This enables easier testing of [safety failsafe](../config/safety.md) behaviour,
55

66
Failure injection is disabled by default, and can be enabled using the [SYS_FAILURE_EN](../advanced_config/parameter_reference.md#SYS_FAILURE_EN) parameter.
77

8-
Failure injection must also be be supported by the current simulator, and the set of supported failures is simulator-dependent.
8+
Failures can be injected both in simulation and on real hardware. In simulation the available failures depend on the simulator. On hardware the `off` (stop publishing) and `stuck` (freeze the last value) types are supported for the `gyro`, `accel`, `mag`, `baro`, `distance_sensor` and `gps` components; this requires firmware built with the failure-injection module.
99

1010
::: info
1111
PX4 may accept a command to set a particular failure mode even it that mode is not supported by your simulator.
@@ -24,7 +24,7 @@ Failures can be injected using the [failure system command](../modules/modules_c
2424
The full syntax of the [failure](../modules/modules_command.md#failure) command is:
2525

2626
```sh
27-
failure <component> <failure_type> [-i <instance_number>]
27+
failure <component> <failure_type> [-i <instance_number>] [-m <instance_bitmask>]
2828
```
2929

3030
where:
@@ -58,11 +58,23 @@ where:
5858
- `intermittent`: Publish intermittently
5959
- _instance number_ (optional): Instance number of affected sensor.
6060
0 (default) indicates all sensors of specified type.
61+
- _instance bitmask_ (optional): address several instances at once (bit 0 = first instance, bit 1 = second, …; decimal or `0x` hex). Used only when `-i` is omitted. Example: `-m 0x5` targets instances 1 and 3.
6162

6263
::: info
6364
The simulated GPS (SITL) implements only the `off`, `stuck`, and `wrong` failure modes; the other failure types have no effect on it.
6465
:::
6566

67+
## RC Switch Trigger
68+
69+
A failure can also be injected from an RC switch, without a console or telemetry link. This is useful for in-flight hardware testing. It is configured with the following parameters:
70+
71+
- [SYS_FAIL_RC_SRC](../advanced_config/parameter_reference.md#SYS_FAIL_RC_SRC): the auxiliary RC input that triggers the failure — `0` disables it, `1``6` select AUX1–AUX6 (mapped via `RC_MAP_AUXn`).
72+
- [SYS_FAIL_RC_UNIT](../advanced_config/parameter_reference.md#SYS_FAIL_RC_UNIT): the affected component (the `FAILURE_UNIT` value; e.g. `101` = motor).
73+
- [SYS_FAIL_RC_MODE](../advanced_config/parameter_reference.md#SYS_FAIL_RC_MODE): the failure type (the `FAILURE_TYPE` value; e.g. `1` = off).
74+
- [SYS_FAIL_RC_INST](../advanced_config/parameter_reference.md#SYS_FAIL_RC_INST): the affected instance (1-based; `0` = all instances).
75+
76+
While the selected aux switch is on the configured failure is injected; switching it back off clears the failure. The injection goes through the same path as the console/MAVLink commands, so for a motor it stops the motor exactly as `failure motor off` does (which also requires [CA_FAILURE_MODE](../advanced_config/parameter_reference.md#CA_FAILURE_MODE)).
77+
6678
## MAVSDK Failure Plugin
6779

6880
The [MAVSDK failure plugin](https://mavsdk.mavlink.io/main/en/cpp/api_reference/classmavsdk_1_1_failure.html) can be used to programmatically inject failures.

msg/FailureInjection.msg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ uint8 FAILURE_UNIT_SYSTEM_SERVO = 102
2929
uint8 FAILURE_UNIT_SYSTEM_AVOIDANCE = 103
3030
uint8 FAILURE_UNIT_SYSTEM_RC_SIGNAL = 104
3131
uint8 FAILURE_UNIT_SYSTEM_MAVLINK_SIGNAL = 105
32+
uint8 FAILURE_UNIT_SYSTEM_ESC = 106
3233

3334
uint16[4] instance_mask # Bit i targets instance (i+1); 0xFFFF = all instances
3435

msg/versioned/VehicleCommand.msg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ uint8 FAILURE_UNIT_SYSTEM_SERVO = 102
170170
uint8 FAILURE_UNIT_SYSTEM_AVOIDANCE = 103
171171
uint8 FAILURE_UNIT_SYSTEM_RC_SIGNAL = 104
172172
uint8 FAILURE_UNIT_SYSTEM_MAVLINK_SIGNAL = 105
173+
uint8 FAILURE_UNIT_SYSTEM_ESC = 106
173174

174175
uint8 FAILURE_TYPE_OK = 0
175176
uint8 FAILURE_TYPE_OFF = 1

src/modules/failure_injection_manager/FailureInjectionManager.cpp

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,26 @@
3636
#include <cmath>
3737

3838
#include <drivers/drv_hrt.h>
39+
#include <px4_platform_common/defines.h>
3940
#include <px4_platform_common/log.h>
4041

4142
ModuleBase::Descriptor FailureInjectionManager::desc{task_spawn, custom_command, print_usage};
4243

4344
FailureInjectionManager::FailureInjectionManager() :
45+
ModuleParams(nullptr),
4446
WorkItem(MODULE_NAME, px4::wq_configurations::lp_default)
4547
{
4648
}
4749

4850
bool FailureInjectionManager::init()
4951
{
5052
if (!_vehicle_command_sub.registerCallback()) {
51-
PX4_ERR("callback registration failed");
53+
PX4_ERR("vehicle_command callback registration failed");
54+
return false;
55+
}
56+
57+
if (!_manual_control_setpoint_sub.registerCallback()) {
58+
PX4_ERR("manual_control_setpoint callback registration failed");
5259
return false;
5360
}
5461

@@ -61,6 +68,7 @@ void FailureInjectionManager::Run()
6168
{
6269
if (should_exit()) {
6370
_vehicle_command_sub.unregisterCallback();
71+
_manual_control_setpoint_sub.unregisterCallback();
6472
exit_and_cleanup(desc);
6573
return;
6674
}
@@ -73,6 +81,8 @@ void FailureInjectionManager::Run()
7381
}
7482
}
7583

84+
evaluateRcInjection();
85+
7686
// Republish only when the configuration actually changed
7787
if (_table.changed()) {
7888
failure_injection_s msg{};
@@ -83,15 +93,64 @@ void FailureInjectionManager::Run()
8393
}
8494
}
8595

96+
void FailureInjectionManager::evaluateRcInjection()
97+
{
98+
manual_control_setpoint_s manual_control_setpoint{};
99+
_manual_control_setpoint_sub.copy(&manual_control_setpoint);
100+
101+
float value = NAN;
102+
103+
if (manual_control_setpoint.valid) {
104+
switch (_param_sys_fail_rc_src.get()) {
105+
case 1: value = manual_control_setpoint.aux1; break;
106+
107+
case 2: value = manual_control_setpoint.aux2; break;
108+
109+
case 3: value = manual_control_setpoint.aux3; break;
110+
111+
case 4: value = manual_control_setpoint.aux4; break;
112+
113+
case 5: value = manual_control_setpoint.aux5; break;
114+
115+
case 6: value = manual_control_setpoint.aux6; break;
116+
117+
default: break; // 0 = disabled, or out of range
118+
}
119+
}
120+
121+
const bool triggered = value > 0.5f;
122+
123+
if (triggered && !_rc_active) {
124+
_rc_active_unit = static_cast<uint8_t>(_param_sys_fail_rc_unit.get());
125+
_rc_active_instance = static_cast<uint8_t>(_param_sys_fail_rc_inst.get());
126+
_rc_active = true;
127+
128+
_table.inject(_rc_active_unit, static_cast<uint8_t>(_param_sys_fail_rc_mode.get()), _rc_active_instance);
129+
130+
} else if (!triggered && _rc_active) {
131+
_table.inject(_rc_active_unit, failure_injection_s::FAILURE_TYPE_OK, _rc_active_instance);
132+
_rc_active = false;
133+
}
134+
}
135+
86136
void FailureInjectionManager::handleCommand(const vehicle_command_s &cmd)
87137
{
88138
const uint8_t unit = static_cast<uint8_t>(lroundf(cmd.param1));
89139
const uint8_t type = static_cast<uint8_t>(lroundf(cmd.param2));
90-
const uint8_t instance = static_cast<uint8_t>(lroundf(cmd.param3));
140+
141+
failure_injection::FailureTable::AckResult ack;
142+
143+
if (PX4_ISFINITE(cmd.param3)) {
144+
ack = _table.inject(unit, type, static_cast<uint8_t>(lroundf(cmd.param3)));
145+
146+
} else {
147+
const uint16_t mask = PX4_ISFINITE(cmd.param4) ? static_cast<uint16_t>(lroundf(cmd.param4)) : 0;
148+
ack = _table.injectMask(unit, type, mask);
149+
}
91150

92151
uint8_t result;
93152

94-
switch (_table.inject(unit, type, instance)) {
153+
switch (ack) {
95154
case failure_injection::FailureTable::AckResult::Accepted:
96155
result = vehicle_command_ack_s::VEHICLE_CMD_RESULT_ACCEPTED;
97156
break;
@@ -157,14 +216,20 @@ int FailureInjectionManager::print_usage(const char *reason)
157216
R"DESCR_STR(
158217
### Description
159218
160-
The failure injection manager is the single subscriber to `vehicle_command` for
161-
`MAV_CMD_INJECT_FAILURE`. It maintains the set of currently active failures and
162-
publishes the `failure_injection` topic, republishing only when the configuration
163-
changes so that command spam cannot propagate to the consumers that apply the
164-
failures. It also produces the central `vehicle_command_ack`.
219+
Central module for handling failure injection. It collects failure requests, tracks
220+
the set of active failures, and publishes them on the `failure_injection` topic for
221+
the apply-sites to act on.
222+
223+
Failures can be triggered through:
224+
- `MAV_CMD_INJECT_FAILURE` over MAVLink (e.g. from MAVSDK)
225+
- the `failure` console command
226+
- an RC switch: `SYS_FAIL_RC_SRC` selects the aux input, and `SYS_FAIL_RC_UNIT` /
227+
`SYS_FAIL_RC_MODE` / `SYS_FAIL_RC_INST` define the failure applied while it is on
228+
229+
Requires `SYS_FAILURE_EN` to be set; the startup script only starts this module when it is.
165230
166-
Failure injection is gated by the `SYS_FAILURE_EN` parameter, which the startup
167-
script checks before starting this module.
231+
Failures can be applied both in simulation and on real hardware, where the apply-sites are
232+
compiled in alongside this module.
168233
169234
)DESCR_STR");
170235

src/modules/failure_injection_manager/FailureInjectionManager.hpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,16 @@
4646
#include "FailureTable.hpp"
4747

4848
#include <px4_platform_common/module.h>
49+
#include <px4_platform_common/module_params.h>
4950
#include <px4_platform_common/px4_work_queue/WorkItem.hpp>
5051
#include <uORB/Publication.hpp>
5152
#include <uORB/SubscriptionCallback.hpp>
5253
#include <uORB/topics/failure_injection.h>
54+
#include <uORB/topics/manual_control_setpoint.h>
5355
#include <uORB/topics/vehicle_command.h>
5456
#include <uORB/topics/vehicle_command_ack.h>
5557

56-
class FailureInjectionManager : public ModuleBase, public px4::WorkItem
58+
class FailureInjectionManager : public ModuleBase, public ModuleParams, public px4::WorkItem
5759
{
5860
public:
5961
FailureInjectionManager();
@@ -78,10 +80,24 @@ class FailureInjectionManager : public ModuleBase, public px4::WorkItem
7880
void handleCommand(const vehicle_command_s &cmd);
7981
void publishAck(const vehicle_command_s &cmd, uint8_t result);
8082

83+
void evaluateRcInjection();
84+
8185
uORB::SubscriptionCallbackWorkItem _vehicle_command_sub{this, ORB_ID(vehicle_command)};
86+
uORB::SubscriptionCallbackWorkItem _manual_control_setpoint_sub{this, ORB_ID(manual_control_setpoint)};
8287

8388
uORB::Publication<failure_injection_s> _failure_injection_pub{ORB_ID(failure_injection)};
8489
uORB::Publication<vehicle_command_ack_s> _command_ack_pub{ORB_ID(vehicle_command_ack)};
8590

8691
failure_injection::FailureTable _table;
92+
93+
bool _rc_active{false};
94+
uint8_t _rc_active_unit{0};
95+
uint8_t _rc_active_instance{0};
96+
97+
DEFINE_PARAMETERS(
98+
(ParamInt<px4::params::SYS_FAIL_RC_SRC>) _param_sys_fail_rc_src,
99+
(ParamInt<px4::params::SYS_FAIL_RC_UNIT>) _param_sys_fail_rc_unit,
100+
(ParamInt<px4::params::SYS_FAIL_RC_MODE>) _param_sys_fail_rc_mode,
101+
(ParamInt<px4::params::SYS_FAIL_RC_INST>) _param_sys_fail_rc_inst
102+
)
87103
};

src/modules/failure_injection_manager/FailureInjectionManagerTest.cpp

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,12 @@ TEST(FailureTable, SupportedCatalogueMatchesInventory)
6262
EXPECT_TRUE(FailureTable::isSupported(GPS, WRONG));
6363
EXPECT_TRUE(FailureTable::isSupported(MOTOR, WRONG));
6464
EXPECT_FALSE(FailureTable::isSupported(GYRO, GARBAGE)); // GARBAGE unimplemented
65+
// Distance sensor (rangefinder) supports OFF/STUCK on hardware, but not WRONG.
66+
EXPECT_TRUE(FailureTable::isSupported(failure_injection_s::FAILURE_UNIT_SENSOR_DISTANCE_SENSOR, OFF));
67+
EXPECT_TRUE(FailureTable::isSupported(failure_injection_s::FAILURE_UNIT_SENSOR_DISTANCE_SENSOR, STUCK));
68+
EXPECT_FALSE(FailureTable::isSupported(failure_injection_s::FAILURE_UNIT_SENSOR_DISTANCE_SENSOR, WRONG));
6569
// Unimplemented units.
6670
EXPECT_FALSE(FailureTable::isSupported(failure_injection_s::FAILURE_UNIT_SYSTEM_RC_SIGNAL, OFF));
67-
EXPECT_FALSE(FailureTable::isSupported(failure_injection_s::FAILURE_UNIT_SENSOR_DISTANCE_SENSOR, OFF));
6871
}
6972

7073
TEST(FailureTable, UnsupportedIsRejectedWithoutChange)
@@ -196,3 +199,33 @@ TEST(FailureTable, AddingInstanceToExistingEntryDoesNotCountAsNew)
196199
// gyro entry now covers instances 1 and 2
197200
EXPECT_EQ(msg.instance_mask[0], 0x1 | 0x2);
198201
}
202+
203+
TEST(FailureTable, InjectMaskSetsMultipleInstances)
204+
{
205+
FailureTable table;
206+
// bits 0 and 2 -> instances 1 and 3 (MAV_CMD_INJECT_FAILURE param4)
207+
EXPECT_EQ(table.injectMask(GYRO, OFF, 0x5), AckResult::Accepted);
208+
EXPECT_TRUE(table.changed());
209+
ASSERT_EQ(table.count(), 1);
210+
211+
failure_injection_s msg{};
212+
table.fill(msg);
213+
EXPECT_EQ(msg.unit[0], GYRO);
214+
EXPECT_EQ(msg.failure_type[0], OFF);
215+
EXPECT_EQ(msg.instance_mask[0], 0x5);
216+
}
217+
218+
TEST(FailureTable, InjectMaskZeroIsAcceptedNoOp)
219+
{
220+
FailureTable table;
221+
EXPECT_EQ(table.injectMask(GYRO, OFF, 0), AckResult::Accepted);
222+
EXPECT_FALSE(table.changed());
223+
EXPECT_EQ(table.count(), 0);
224+
}
225+
226+
TEST(FailureTable, InjectMaskUnsupportedIsRejected)
227+
{
228+
FailureTable table;
229+
EXPECT_EQ(table.injectMask(GYRO, WRONG, 0x1), AckResult::Unsupported);
230+
EXPECT_EQ(table.count(), 0);
231+
}

src/modules/failure_injection_manager/FailureTable.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ bool FailureTable::isSupported(uint8_t unit, uint8_t type)
4747
case failure_injection_s::FAILURE_UNIT_SENSOR_ACCEL:
4848
case failure_injection_s::FAILURE_UNIT_SENSOR_MAG:
4949
case failure_injection_s::FAILURE_UNIT_SENSOR_BARO:
50+
case failure_injection_s::FAILURE_UNIT_SENSOR_DISTANCE_SENSOR:
5051
return type == failure_injection_s::FAILURE_TYPE_OK
5152
|| type == failure_injection_s::FAILURE_TYPE_OFF
5253
|| type == failure_injection_s::FAILURE_TYPE_STUCK;
@@ -129,14 +130,17 @@ void FailureTable::compact()
129130
}
130131

131132
FailureTable::AckResult FailureTable::inject(uint8_t unit, uint8_t type, uint8_t instance)
133+
{
134+
return injectMask(unit, type, instanceToMask(instance));
135+
}
136+
137+
FailureTable::AckResult FailureTable::injectMask(uint8_t unit, uint8_t type, uint16_t mask)
132138
{
133139
// Validate the request against the capability catalogue.
134140
if (!isSupported(unit, type)) {
135141
return AckResult::Unsupported;
136142
}
137143

138-
const uint16_t mask = instanceToMask(instance);
139-
140144
if (mask == 0) {
141145
// Out-of-range instance: nothing addressable to do.
142146
return AckResult::Accepted;

0 commit comments

Comments
 (0)