Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions msg/TecsStatus.msg
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ float32 throttle_trim # estimated throttle value [0,1] required to fly level a

float32 underspeed_ratio # 0: no underspeed, 1: maximal underspeed. Controller takes measures to avoid stall proportional to ratio if >0.
float32 fast_descend_ratio # value indicating if fast descend mode is enabled with ramp up and ramp down [0-1]

float32 weight_ratio # Ratio of estimated vehicle weight to WEIGHT_BASE, accounting for burned fuel if fuel-based weight compensation is enabled [-]
3 changes: 3 additions & 0 deletions src/lib/fw_performance_model/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@
px4_add_library(performance_model
PerformanceModel.cpp
)
target_link_libraries(performance_model PRIVATE atmosphere geo)
set_property(GLOBAL APPEND PROPERTY PX4_MODULE_CONFIG_FILES ${CMAKE_CURRENT_SOURCE_DIR}/performance_model_params.yaml)

px4_add_functional_gtest(SRC PerformanceModelTest.cpp LINKLIBS performance_model)
32 changes: 31 additions & 1 deletion src/lib/fw_performance_model/PerformanceModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,42 @@ PerformanceModel::PerformanceModel(): ModuleParams(nullptr)
{
updateParams();
}
void PerformanceModel::setFuelFractionRemaining(float fuel_fraction_remaining)
{
if (PX4_ISFINITE(fuel_fraction_remaining)) {
_fuel_fraction_remaining = fuel_fraction_remaining;
}
}

float PerformanceModel::getFuelFractionRemaining(const fuel_tank_status_s &fuel_tank_status)
{
float fraction = NAN;

if (PX4_ISFINITE(fuel_tank_status.remaining_fuel) && fuel_tank_status.maximum_fuel_capacity > FLT_EPSILON) {
fraction = fuel_tank_status.remaining_fuel / fuel_tank_status.maximum_fuel_capacity;

} else if (fuel_tank_status.percent_remaining <= 100) {
fraction = fuel_tank_status.percent_remaining * 0.01f;

} else if (PX4_ISFINITE(fuel_tank_status.consumed_fuel) && fuel_tank_status.maximum_fuel_capacity > FLT_EPSILON) {
fraction = 1.f - fuel_tank_status.consumed_fuel / fuel_tank_status.maximum_fuel_capacity;
}

return PX4_ISFINITE(fraction) ? math::constrain(fraction, 0.f, 1.f) : NAN;
}

float PerformanceModel::getWeightRatio() const
{
float weight_factor = 1.0f;

if (_param_weight_base.get() > FLT_EPSILON && _param_weight_gross.get() > FLT_EPSILON) {
weight_factor = math::constrain(_param_weight_gross.get() / _param_weight_base.get(), kMinWeightRatio,
float weight = _param_weight_gross.get();

if (_param_weight_fuel.get() > FLT_EPSILON && PX4_ISFINITE(_fuel_fraction_remaining)) {
weight -= (1.f - math::constrain(_fuel_fraction_remaining, 0.f, 1.f)) * _param_weight_fuel.get();
Comment thread
mahima-yoga marked this conversation as resolved.
Outdated
}

weight_factor = math::constrain(weight / _param_weight_base.get(), kMinWeightRatio,
kMaxWeightRatio);
}

Expand Down
22 changes: 22 additions & 0 deletions src/lib/fw_performance_model/PerformanceModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
* Performance model.
*/

#include <math.h>
#include <px4_platform_common/module_params.h>
#include <uORB/topics/fuel_tank_status.h>

#ifndef PX4_SRC_MODULES_FW_POS_CONTROL_PERFORMANCEMODEL_H_
#define PX4_SRC_MODULES_FW_POS_CONTROL_PERFORMANCEMODEL_H_
Expand Down Expand Up @@ -70,6 +72,23 @@ class PerformanceModel : public ModuleParams
*/
float getWeightRatio() const;

/**
* Set the fraction of fuel remaining, used to reduce the vehicle weight by the burned fuel mass
* if fuel-based weight compensation is enabled (WEIGHT_FUEL > 0).
* Non-finite input is ignored and the last known fuel fraction is kept.
* @param fuel_fraction_remaining fraction of fuel remaining in range [0,1]
*/
void setFuelFractionRemaining(float fuel_fraction_remaining);

/**
* Extract the fraction of fuel remaining from a fuel tank status message, preferring the
* measured remaining fuel over the remaining percentage over the consumed fuel.
* The consumed fuel fallback assumes the tank was full at boot.
* @param fuel_tank_status fuel tank status message
* @return fraction of fuel remaining in range [0,1], NAN if not available
*/
static float getFuelFractionRemaining(const fuel_tank_status_s &fuel_tank_status);

/**
* Get the trim throttle for the current airspeed setpoint as well as air density and weight.
* @param throttle_min minimum throttle in range [0,1]
Expand Down Expand Up @@ -131,6 +150,7 @@ class PerformanceModel : public ModuleParams
(ParamFloat<px4::params::FW_T_SINK_MIN>) _param_fw_t_sink_min,
(ParamFloat<px4::params::WEIGHT_BASE>) _param_weight_base,
(ParamFloat<px4::params::WEIGHT_GROSS>) _param_weight_gross,
(ParamFloat<px4::params::WEIGHT_FUEL>) _param_weight_fuel,
(ParamFloat<px4::params::FW_SERVICE_CEIL>) _param_service_ceiling,
(ParamFloat<px4::params::FW_THR_TRIM>) _param_fw_thr_trim,
(ParamFloat<px4::params::FW_THR_MAX>) _param_fw_thr_max,
Expand All @@ -150,6 +170,8 @@ class PerformanceModel : public ModuleParams


static float sanitiseAirDensity(float air_density);

float _fuel_fraction_remaining{NAN}; ///< fraction of fuel remaining in range [0,1], NAN if unknown
};

#endif //PX4_SRC_MODULES_FW_POS_CONTROL_PERFORMANCEMODEL_H_
181 changes: 181 additions & 0 deletions src/lib/fw_performance_model/PerformanceModelTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/****************************************************************************
*
* Copyright (c) 2026 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/

#include <gtest/gtest.h>
#include "PerformanceModel.hpp"

// to run: make tests TESTFILTER=PerformanceModel

class PerformanceModelTest : public ::testing::Test
{
public:
void SetUp() override
{
param_control_autosave(false);
param_reset_all();
}

void setWeightParams(float base, float gross, float fuel)
{
param_set(param_handle(px4::params::WEIGHT_BASE), &base);
param_set(param_handle(px4::params::WEIGHT_GROSS), &gross);
param_set(param_handle(px4::params::WEIGHT_FUEL), &fuel);
_model.updateParameters();
}

PerformanceModel _model;
};

TEST_F(PerformanceModelTest, weightRatioDefaultsToOne)
{
// GIVEN: default parameters (weight compensation disabled)
// THEN: the weight ratio is 1
EXPECT_FLOAT_EQ(_model.getWeightRatio(), 1.0f);
}

TEST_F(PerformanceModelTest, weightRatioWithoutFuelCompensation)
{
// GIVEN: base and gross weight set, fuel compensation disabled
setWeightParams(10.0f, 12.0f, -1.0f);

// THEN: the weight ratio is gross/base, independent of any fuel fraction
EXPECT_FLOAT_EQ(_model.getWeightRatio(), 1.2f);

_model.setFuelFractionRemaining(0.5f);
EXPECT_FLOAT_EQ(_model.getWeightRatio(), 1.2f);
}

TEST_F(PerformanceModelTest, weightRatioScalesWithFuelFraction)
{
// GIVEN: base and gross weight set, 4kg full fuel load
setWeightParams(10.0f, 12.0f, 4.0f);

// THEN: with unknown fuel fraction the full-tank gross weight is assumed
EXPECT_FLOAT_EQ(_model.getWeightRatio(), 1.2f);

// AND: the weight ratio scales linearly with the burned fuel mass
_model.setFuelFractionRemaining(1.0f);
EXPECT_FLOAT_EQ(_model.getWeightRatio(), 1.2f);

_model.setFuelFractionRemaining(0.5f);
EXPECT_FLOAT_EQ(_model.getWeightRatio(), 1.0f);

_model.setFuelFractionRemaining(0.0f);
EXPECT_FLOAT_EQ(_model.getWeightRatio(), 0.8f);
}

TEST_F(PerformanceModelTest, fuelFractionKeptOnNonFiniteInput)
{
// GIVEN: base and gross weight set, 4kg full fuel load, and a known fuel fraction
setWeightParams(10.0f, 12.0f, 4.0f);
_model.setFuelFractionRemaining(0.5f);
EXPECT_FLOAT_EQ(_model.getWeightRatio(), 1.0f);

// WHEN: the fuel fraction input becomes non-finite (e.g. sensor failure)
_model.setFuelFractionRemaining(NAN);

// THEN: the last known fuel fraction is kept
EXPECT_FLOAT_EQ(_model.getWeightRatio(), 1.0f);
}

TEST_F(PerformanceModelTest, fuelFractionIsConstrained)
{
// GIVEN: base and gross weight set, 4kg full fuel load
setWeightParams(10.0f, 12.0f, 4.0f);

// THEN: out-of-range fuel fractions are constrained to [0, 1]
_model.setFuelFractionRemaining(-0.5f);
EXPECT_FLOAT_EQ(_model.getWeightRatio(), 0.8f);

_model.setFuelFractionRemaining(1.5f);
EXPECT_FLOAT_EQ(_model.getWeightRatio(), 1.2f);
}

TEST_F(PerformanceModelTest, weightRatioIsConstrained)
{
// GIVEN: a fuel weight larger than the gross weight (misconfiguration)
setWeightParams(10.0f, 12.0f, 12.0f);

// THEN: the weight ratio is constrained to its lower bound
_model.setFuelFractionRemaining(0.0f);
EXPECT_FLOAT_EQ(_model.getWeightRatio(), 0.5f);
}

TEST_F(PerformanceModelTest, fuelFractionFromTankStatusFallbackChain)
{
// GIVEN: a fuel tank status with no measurements
fuel_tank_status_s status{};
status.maximum_fuel_capacity = NAN;
status.remaining_fuel = NAN;
status.consumed_fuel = NAN;
status.percent_remaining = UINT8_MAX;

// THEN: no fuel fraction is available
EXPECT_TRUE(std::isnan(PerformanceModel::getFuelFractionRemaining(status)));

// AND: the consumed fuel is the last fallback, assuming a full tank at boot
status.maximum_fuel_capacity = 2000.f;
status.consumed_fuel = 500.f;
EXPECT_FLOAT_EQ(PerformanceModel::getFuelFractionRemaining(status), 0.75f);

// AND: a valid remaining percentage takes precedence over the consumed fuel
status.percent_remaining = 40;
EXPECT_FLOAT_EQ(PerformanceModel::getFuelFractionRemaining(status), 0.4f);

// AND: the measured remaining fuel takes precedence over everything else
status.remaining_fuel = 1000.f;
EXPECT_FLOAT_EQ(PerformanceModel::getFuelFractionRemaining(status), 0.5f);
}

TEST_F(PerformanceModelTest, fuelFractionFromTankStatusIsConstrained)
{
// GIVEN: a remaining fuel measurement exceeding the maximum capacity
fuel_tank_status_s status{};
status.maximum_fuel_capacity = 2000.f;
status.remaining_fuel = 3000.f;
status.consumed_fuel = NAN;
status.percent_remaining = UINT8_MAX;

// THEN: the fuel fraction is constrained to 1
EXPECT_FLOAT_EQ(PerformanceModel::getFuelFractionRemaining(status), 1.0f);

// AND: more consumed fuel than the maximum capacity results in a fraction of 0
status.remaining_fuel = NAN;
status.consumed_fuel = 3000.f;
EXPECT_FLOAT_EQ(PerformanceModel::getFuelFractionRemaining(status), 0.0f);

// AND: a remaining fuel measurement without a valid maximum capacity is unusable
status.maximum_fuel_capacity = 0.f;
status.remaining_fuel = 1000.f;
EXPECT_TRUE(std::isnan(PerformanceModel::getFuelFractionRemaining(status)));
}
15 changes: 15 additions & 0 deletions src/lib/fw_performance_model/performance_model_params.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ parameters:
This is the actual weight of the vehicle at any time. This value will differ from WEIGHT_BASE in case weight was added
or removed from the base weight. Examples are the addition of payloads or larger batteries. A zero or negative value
disables trim throttle and minimum airspeed compensation based on weight.
If fuel-based weight compensation is enabled (WEIGHT_FUEL), set this to the takeoff weight with a full fuel load.
type: float
default: -1.0
unit: kg
decimal: 1
increment: 0.1
WEIGHT_FUEL:
description:
short: Weight of a full fuel load
long: |-
Mass of the fuel at maximum fuel tank capacity. If set, together with WEIGHT_BASE
and WEIGHT_GROSS, the vehicle weight used for performance scaling is reduced by the burned fuel mass derived from
the fuel tank status. WEIGHT_GROSS then corresponds to the takeoff weight with a full fuel load. A zero or negative
value disables fuel-based weight compensation.
Only a single fuel tank is supported: reports from additional tank ids are ignored.
Comment thread
mahima-yoga marked this conversation as resolved.
Outdated
type: float
default: -1.0
unit: kg
Expand Down
Loading
Loading