This tutorial describes how the Force Allocation Module (FAM) is integrated in Gazebo via a ModelPlugin.
FAM is the module that maps the desired overall force and torque acting on a free-flyer to the individual thrust forces required at each propeller. This allows a high-level controller to command abstract movements (like “move forward” or “rotate”) while the FAM handles the specific hardware actuation.
The relationship between desired forces and propeller thrust is defined by:
\[\mathbf{T} \cdot \mathbf{f}_{prop} = \mathbf{f}_{des}\]Definitions:
In overdetermined systems (where there are more propellers than degrees of freedom), $\mathbf{T}$ cannot be directly inverted. Instead, we use the Moore-Penrose Pseudo-inverse ($\mathbf{T}^{+}$) to find the least-squares solution:
\[\mathbf{f}_{prop} = \mathbf{T}^{+} \cdot \mathbf{f}_{des}\]This approach distributes the load across all available thrusters efficiently.
The FAM logic is integrated into Gazebo by compiling the .cpp plugin file via CMakeLists.txt.
geometry_msgs/Wrench) to receive the desired effort vector $\mathbf{f}_{des}$./actuator_force_goal topic.
While AddForceAtRelativePosition applies force at a specific point, it does not automatically align the force vector with the robot’s changing orientation. To ensure the force is always exerted in the correct outward-facing direction, you must first retrieve the current world pose of the free-flyer.
Code Snippet (Example for 1 Thruster):
// 1. Get the current rotation (pose) of the robot in the world
ignition::math::Quaterniond q = this->model->GetLink("base_link")->WorldPose().Rot();
// 2. Transform the local thrust direction (e.g., +X) into the world frame
ignition::math::Vector3d force0 = q * ignition::math::Vector3d(1.0, 0.0, 0.0);
// 3. Define the relative position of the thruster on the body
ignition::math::Vector3d pos0(-0.08, -0.08, 0.0);
// 4. Apply the calculated force at the specified position
this->model->GetLink("base_link")->AddForceAtRelativePosition(this->thrust_values[0] * force0, pos0);