Skip to content

Commit 3707484

Browse files
committed
replace Int32 with Float32MultiArray for ratio-based partial failure
1 parent e9d0afa commit 3707484

4 files changed

Lines changed: 92 additions & 54 deletions

File tree

src/realgazebo/models/lc_62.sdf.jinja

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,8 @@
13041304
<rotorVelocitySlowdownSim>20</rotorVelocitySlowdownSim>
13051305
<motorType>velocity</motorType>
13061306
</plugin>
1307+
<plugin filename="MotorFailureROS2" name="custom::MotorFailureROS2System">
1308+
</plugin>
13071309
<plugin filename="libRealGazebo.so" name="custom::RealGazebo">
13081310
<unreal_ip>{{ unreal_ip }}</unreal_ip>
13091311
<unreal_port>{{ unreal_port }}</unreal_port>

src/realgazebo/plugins/motor_failure_ros2/MotorFailureSystem.cpp

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
#include "MotorFailureSystem.hpp"
3838

39+
#include <algorithm>
3940
#include <gz/plugin/Register.hh>
4041
#include <gz/sim/components/JointVelocityCmd.hh>
4142
#include <gz/sim/components/Name.hh>
@@ -98,13 +99,13 @@ void MotorFailureROS2System::Configure(const gz::sim::Entity &_entity,
9899
// Get model name to use as namespace
99100
std::string model_name = this->model_.Name(_ecm);
100101

101-
// Get ROS2 topic name for motor failure number subscription
102+
// Get ROS2 topic name for motor failure ratios subscription
102103
if (_sdf->HasElement("MotorFailureTopic")) {
103104
this->ros_topic_ = _sdf->Get<std::string>("MotorFailureTopic");
104105

105106
} else {
106107
// Use model name for topic naming
107-
this->ros_topic_ = "/" + model_name + "/motor_failure/motor_number";
108+
this->ros_topic_ = "/" + model_name + "/motor_failure/ratios";
108109
}
109110

110111
// Initialize ROS2, if it has not already been initialized
@@ -118,9 +119,9 @@ void MotorFailureROS2System::Configure(const gz::sim::Entity &_entity,
118119
this->ros_node_ = rclcpp::Node::make_shared("motor_failure");
119120

120121
// Create ROS2 subscription
121-
this->motor_failure_sub_ = this->ros_node_->create_subscription<std_msgs::msg::Int32>(
122+
this->motor_failure_sub_ = this->ros_node_->create_subscription<std_msgs::msg::Float32MultiArray>(
122123
this->ros_topic_, 10,
123-
std::bind(&MotorFailureROS2System::MotorFailureNumberCallback, this, std::placeholders::_1));
124+
std::bind(&MotorFailureROS2System::MotorFailureRatiosCallback, this, std::placeholders::_1));
124125

125126
gzmsg << "[MotorFailureROS2System] Subscribed to ROS2 topic: " << this->ros_topic_ << std::endl;
126127
gzmsg << "[MotorFailureROS2System] Initialized for model: " << model_name << std::endl;
@@ -192,38 +193,63 @@ void MotorFailureROS2System::FindMotorJoints(gz::sim::EntityComponentManager &_e
192193
//////////////////////////////////////////////////
193194
void MotorFailureROS2System::ApplyMotorFailure(gz::sim::EntityComponentManager &_ecm)
194195
{
195-
int32_t current_failure;
196+
std::vector<float> current_ratios;
196197
{
197198
std::lock_guard<std::mutex> lock(this->motor_failure_mutex_);
198-
current_failure = this->motor_failure_number_;
199+
current_ratios = this->motor_failure_ratios_;
199200
}
200201

201202
// Check if failure status changed
202-
if (current_failure != this->prev_motor_failure_number_) {
203-
if (current_failure > 0) {
204-
gzerr << "[MotorFailureROS2System] Motor " << current_failure << " failed!" << std::endl;
203+
if (current_ratios != this->prev_motor_failure_ratios_) {
204+
for (size_t i = 0; i < current_ratios.size(); ++i) {
205+
float ratio = current_ratios[i];
206+
float prev = (i < this->prev_motor_failure_ratios_.size()) ? this->prev_motor_failure_ratios_[i] : 0.0f;
205207

206-
} else if (current_failure == 0 && this->prev_motor_failure_number_ > 0) {
207-
gzerr << "[MotorFailureROS2System] Motor " << this->prev_motor_failure_number_
208-
<< " recovered!" << std::endl;
208+
if (ratio != prev) {
209+
if (ratio >= 1.0f) {
210+
gzerr << "[MotorFailureROS2System] Motor " << i << " completely failed!" << std::endl;
211+
212+
} else if (ratio > 0.0f) {
213+
gzerr << "[MotorFailureROS2System] Motor " << i << " degraded to "
214+
<< (1.0f - ratio) * 100.0f << "% output" << std::endl;
215+
216+
} else {
217+
gzerr << "[MotorFailureROS2System] Motor " << i << " recovered!" << std::endl;
218+
}
219+
}
220+
}
221+
222+
// Check motors that were in the old list but not in the new (implicitly recovered)
223+
for (size_t i = current_ratios.size(); i < this->prev_motor_failure_ratios_.size(); ++i) {
224+
if (this->prev_motor_failure_ratios_[i] > 0.0f) {
225+
gzerr << "[MotorFailureROS2System] Motor " << i << " recovered!" << std::endl;
226+
}
209227
}
210228

211-
this->prev_motor_failure_number_ = current_failure;
229+
this->prev_motor_failure_ratios_ = current_ratios;
212230
}
213231

214-
// Apply motor failure if active (1-indexed from ROS2, convert to 0-indexed)
215-
if (current_failure > 0 && current_failure <= static_cast<int32_t>(this->motor_joints_.size())) {
216-
int motorIdx = current_failure - 1;
217-
Entity jointEntity = this->motor_joints_[motorIdx];
232+
// Apply per-motor failure ratios
233+
for (size_t i = 0; i < this->motor_joints_.size(); ++i) {
234+
float ratio = (i < current_ratios.size()) ? current_ratios[i] : 0.0f;
235+
ratio = std::clamp(ratio, 0.0f, 1.0f);
218236

219-
if (jointEntity != kNullEntity) {
220-
// Force joint velocity command to 0
221-
// This is done in PreUpdate to override MulticopterMotorModel's commands
222-
auto jointVelCmd = _ecm.Component<components::JointVelocityCmd>(jointEntity);
237+
if (ratio <= 0.0f) {
238+
continue;
239+
}
223240

224-
if (jointVelCmd) {
225-
*jointVelCmd = components::JointVelocityCmd({0.0});
226-
}
241+
Entity jointEntity = this->motor_joints_[i];
242+
243+
if (jointEntity == kNullEntity) {
244+
continue;
245+
}
246+
247+
auto jointVelCmd = _ecm.Component<components::JointVelocityCmd>(jointEntity);
248+
249+
if (jointVelCmd) {
250+
double current = jointVelCmd->Data()[0];
251+
double degraded = current * (1.0 - static_cast<double>(ratio));
252+
*jointVelCmd = components::JointVelocityCmd({degraded});
227253
}
228254
}
229255
}
@@ -253,12 +279,12 @@ void MotorFailureROS2System::PreUpdate(const gz::sim::UpdateInfo &_info,
253279
}
254280

255281
//////////////////////////////////////////////////
256-
void MotorFailureROS2System::MotorFailureNumberCallback(const std_msgs::msg::Int32::SharedPtr _msg)
282+
void MotorFailureROS2System::MotorFailureRatiosCallback(const std_msgs::msg::Float32MultiArray::SharedPtr _msg)
257283
{
258284
std::lock_guard<std::mutex> lock(this->motor_failure_mutex_);
259-
this->motor_failure_number_ = _msg->data;
260-
gzdbg << "[MotorFailureROS2System] Received motor failure number: "
261-
<< this->motor_failure_number_ << std::endl;
285+
this->motor_failure_ratios_ = _msg->data;
286+
gzdbg << "[MotorFailureROS2System] Received motor failure ratios, size: "
287+
<< this->motor_failure_ratios_.size() << std::endl;
262288
}
263289

264290
// Register the plugin

src/realgazebo/plugins/motor_failure_ros2/MotorFailureSystem.hpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050

5151
// ROS2 includes
5252
#include <rclcpp/rclcpp.hpp>
53-
#include <std_msgs/msg/int32.hpp>
53+
#include <std_msgs/msg/float32_multi_array.hpp>
5454

5555
namespace custom
5656
{
@@ -81,8 +81,8 @@ class MotorFailureROS2System:
8181
gz::sim::EntityComponentManager &_ecm) override;
8282

8383
private:
84-
/// \brief Callback for ROS2 motor failure number subscription
85-
void MotorFailureNumberCallback(const std_msgs::msg::Int32::SharedPtr _msg);
84+
/// \brief Callback for ROS2 motor failure ratios subscription
85+
void MotorFailureRatiosCallback(const std_msgs::msg::Float32MultiArray::SharedPtr _msg);
8686

8787
/// \brief Find all motor joints in the model
8888
void FindMotorJoints(gz::sim::EntityComponentManager &_ecm);
@@ -93,8 +93,8 @@ class MotorFailureROS2System:
9393
/// \brief ROS2 node for communication
9494
rclcpp::Node::SharedPtr ros_node_;
9595

96-
/// \brief ROS2 subscription for motor failure number
97-
rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr motor_failure_sub_;
96+
/// \brief ROS2 subscription for motor failure ratios
97+
rclcpp::Subscription<std_msgs::msg::Float32MultiArray>::SharedPtr motor_failure_sub_;
9898

9999
/// \brief Model entity
100100
gz::sim::Entity model_entity_;
@@ -105,15 +105,15 @@ class MotorFailureROS2System:
105105
/// \brief Vector of motor joint entities (indexed by motor number)
106106
std::vector<gz::sim::Entity> motor_joints_;
107107

108-
/// \brief Current motor failure number (-1 or 0 means no failure, 1-indexed motor number)
109-
int32_t motor_failure_number_{-1};
108+
/// \brief Current motor failure ratios (0.0=normal, 1.0=complete failure)
109+
std::vector<float> motor_failure_ratios_;
110110

111-
/// \brief Previous motor failure number to detect changes
112-
int32_t prev_motor_failure_number_{-1};
111+
/// \brief Previous motor failure ratios to detect changes
112+
std::vector<float> prev_motor_failure_ratios_;
113113

114114
/// \brief ROS2 topic name for subscribing to motor failure commands
115-
/// Defaults to /<model_name>/motor_failure/motor_number if not specified in SDF
116-
std::string ros_topic_{"/motor_failure/motor_number"};
115+
/// Defaults to /<model_name>/motor_failure/ratios if not specified in SDF
116+
std::string ros_topic_{"/motor_failure/ratios"};
117117

118118
/// \brief Mutex to protect motor_failure_number_
119119
std::mutex motor_failure_mutex_;

src/realgazebo/plugins/motor_failure_ros2/README.md

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,19 @@ The Motor Failure System plugin subscribes to a ROS2 topic to receive motor fail
88

99
## Features
1010

11-
- ROS2 integration for receiving motor failure commands
11+
- ROS2 integration for receiving motor failure commands (ratio-based partial failures)
1212
- Automatic model name detection (no manual namespace configuration needed)
1313
- Automatic detection of motor joints (rotor_0_joint, rotor_1_joint, etc.)
14-
- Direct joint velocity override in PreUpdate cycle
15-
- Thread-safe motor failure number handling
14+
- Direct joint velocity degradation in PreUpdate cycle
15+
- Thread-safe motor failure ratio handling
1616
- Configurable topic names via SDF
1717

1818
## Configuration
1919

2020
### SDF Parameters
2121

2222
- `<MotorFailureTopic>` (optional): ROS2 topic for receiving motor failure commands
23-
- Default: `/<model_name>/motor_failure/motor_number`
23+
- Default: `/<model_name>/motor_failure/ratios`
2424
- The model name is automatically detected from the entity name
2525

2626
### Example SDF Usage
@@ -55,24 +55,34 @@ The Motor Failure System plugin subscribes to a ROS2 topic to receive motor fail
5555

5656
### Publishing Motor Failure Commands
5757

58-
To trigger a motor failure, publish a message to the ROS2 topic.
58+
To trigger a motor failure, publish a `Float32MultiArray` to the ROS2 topic.
59+
Each element `data[i]` is the failure ratio for motor `i` (0-indexed):
60+
- `0.0` = normal operation
61+
- `0.5` = 50% output reduction
62+
- `1.0` = complete failure
5963

6064
For a vehicle with model name `x500_0`:
6165

6266
```bash
63-
# Fail motor 1 (motors are 1-indexed: 1, 2, 3, 4, ...)
64-
ros2 topic pub --once /x500_0/motor_failure/motor_number std_msgs/msg/Int32 "data: 1"
67+
# Fail motor 0 by 50% (partial failure)
68+
ros2 topic pub --once /x500_0/motor_failure/ratios \
69+
std_msgs/msg/Float32MultiArray "data: [0.5, 0.0, 0.0, 0.0]"
6570

66-
# Clear motor failure (restore normal operation)
67-
ros2 topic pub --once /x500_0/motor_failure/motor_number std_msgs/msg/Int32 "data: 0"
71+
# Completely fail motor 0
72+
ros2 topic pub --once /x500_0/motor_failure/ratios \
73+
std_msgs/msg/Float32MultiArray "data: [1.0, 0.0, 0.0, 0.0]"
74+
75+
# Restore all motors to normal
76+
ros2 topic pub --once /x500_0/motor_failure/ratios \
77+
std_msgs/msg/Float32MultiArray "data: [0.0, 0.0, 0.0, 0.0]"
6878
```
6979

7080
**Note**: Replace `x500_0` with your model's name (automatically detected from the entity name).
7181

72-
**Motor Numbering**:
73-
- Motors are **1-indexed**: 1, 2, 3, 4, etc.
74-
- `data: 0` clears the motor failure
75-
- `data: -1` also clears the motor failure
82+
**Motor Indexing**:
83+
- Motors are **0-indexed** matching `rotor_0_joint`, `rotor_1_joint`, etc.
84+
- Arrays shorter than the motor count leave remaining motors at normal (0.0)
85+
- Ratios are clamped to `[0.0, 1.0]`
7686

7787
### Monitoring Motor Failure Status
7888

@@ -123,7 +133,7 @@ source install/setup.bash
123133
## Notes
124134

125135
- The plugin automatically initializes ROS2 if not already initialized
126-
- Motor failure number is thread-safe protected with a mutex
127-
- The plugin applies motor failure in the PreUpdate cycle by setting joint velocity to 0
136+
- Motor failure ratios are thread-safe protected with a mutex
137+
- The plugin applies motor failure in the PreUpdate cycle by scaling the current joint velocity command
128138
- **Plugin Declaration Order**: This plugin must be declared AFTER the MulticopterMotorModel plugin in the SDF file to ensure proper execution order
129139
- The plugin automatically detects the model name from the entity, eliminating the need for manual namespace configuration

0 commit comments

Comments
 (0)