PX4-Autopilot/src/modules/rc_update/RCUpdateTest.cpp
Beat Küng 9472b4b1f7 refactor: remove '#define MODULE_NAME' from tests
And use target_compile_definitions() instead
2025-07-11 10:39:28 +02:00

327 lines
16 KiB
C++

/****************************************************************************
*
* Copyright (C) 2022 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 "rc_update.h"
using namespace rc_update;
class TestRCUpdate : public RCUpdate
{
public:
void UpdateManualSwitches(const hrt_abstime &timestamp_sample) { RCUpdate::UpdateManualSwitches(timestamp_sample); }
void updateParams() { RCUpdate::updateParams(); }
void setChannel(size_t index, float channel_value) { _rc.channels[index] = channel_value; }
};
class RCUpdateTest : public ::testing::Test, ModuleParams
{
public:
RCUpdateTest() : ModuleParams(nullptr)
{
// Disable autosaving parameters to avoid busy loop in param_set()
param_control_autosave(false);
}
void checkModeSlotSwitch(float channel_value, uint8_t expected_slot)
{
// GIVEN: First channel is configured as mode switch
_param_rc_map_fltmode.set(1);
_param_rc_map_fltmode.commit();
EXPECT_EQ(_param_rc_map_fltmode.get(), 1);
_rc_update.updateParams();
// GIVEN: First channel has some value
_rc_update.setChannel(0, channel_value);
// WHEN: we update the switches two times to pass the simple outlier protection
_rc_update.UpdateManualSwitches(0);
_rc_update.UpdateManualSwitches(0);
// THEN: we receive the expected mode slot
uORB::SubscriptionData<manual_control_switches_s> manual_control_switches_sub{ORB_ID(manual_control_switches)};
EXPECT_EQ(manual_control_switches_sub.get().mode_slot, expected_slot);
}
void checkModeSlotButton(uint8_t button_bitmask, uint8_t channel, float channel_value, uint8_t expected_slot)
{
// GIVEN: No mode switch is mapped
_param_rc_map_fltmode.set(0);
_param_rc_map_fltmode.commit();
EXPECT_EQ(_param_rc_map_fltmode.get(), 0);
// GIVEN: Buttons are configured
_param_rc_map_fltm_btn.set(button_bitmask);
_param_rc_map_fltm_btn.commit();
EXPECT_EQ(_param_rc_map_fltm_btn.get(), button_bitmask);
_rc_update.updateParams();
// GIVEN: First channel has some value
_rc_update.setChannel(channel - 1, channel_value);
// WHEN: we update the switches 4 times:
// - initiate the button press
// - keep the same button pressed
// - hold the button for 50ms
// - pass the simple outlier protection
_rc_update.UpdateManualSwitches(0);
_rc_update.UpdateManualSwitches(0);
_rc_update.UpdateManualSwitches(51_ms);
_rc_update.UpdateManualSwitches(51_ms);
// THEN: we receive the expected mode slot
uORB::SubscriptionData<manual_control_switches_s> manual_control_switches_sub{ORB_ID(manual_control_switches)};
EXPECT_EQ(manual_control_switches_sub.get().mode_slot, expected_slot);
// Reset channel value for the next test
_rc_update.setChannel(channel - 1, 0.f);
}
void checkReturnSwitch(float channel_value, float threshold, uint8_t expected_position)
{
// GIVEN: First channel is configured as return switch
_param_rc_map_return_sw.set(1);
_param_rc_map_return_sw.commit();
_param_rc_return_th.set(threshold);
_param_rc_return_th.commit();
_rc_update.updateParams();
EXPECT_EQ(_param_rc_map_return_sw.get(), 1);
EXPECT_FLOAT_EQ(_param_rc_return_th.get(), threshold);
// GIVEN: First channel has some value
_rc_update.setChannel(0, channel_value);
// WHEN: we update the switches two times to pass the simple outlier protection
_rc_update.UpdateManualSwitches(0);
_rc_update.UpdateManualSwitches(0);
// THEN: we receive the expected mode slot
uORB::SubscriptionData<manual_control_switches_s> manual_control_switches_sub{ORB_ID(manual_control_switches)};
EXPECT_EQ(manual_control_switches_sub.get().return_switch, expected_position);
}
void checkPayloadPowerSwitch(float channel_value, float on_threshold, float mid_threshold, uint8_t expected_position)
{
// GIVEN: First channel is configured as payload power switch
_param_rc_map_pay_sw.set(1);
_param_rc_map_pay_sw.commit();
_param_rc_payload_th.set(on_threshold);
_param_rc_payload_th.commit();
_param_rc_payload_midth.set(mid_threshold);
_param_rc_payload_midth.commit();
_rc_update.updateParams();
EXPECT_EQ(_param_rc_map_pay_sw.get(), 1);
EXPECT_FLOAT_EQ(_param_rc_payload_th.get(), on_threshold);
EXPECT_FLOAT_EQ(_param_rc_payload_midth.get(), mid_threshold);
// GIVEN: First channel has some value
_rc_update.setChannel(0, channel_value);
// WHEN: we update the switches two times to pass the simple outlier protection
_rc_update.UpdateManualSwitches(0);
_rc_update.UpdateManualSwitches(0);
// THEN: we receive the expected mode slot
uORB::SubscriptionData<manual_control_switches_s> manual_control_switches_sub{ORB_ID(manual_control_switches)};
EXPECT_EQ(manual_control_switches_sub.get().payload_power_switch, expected_position);
}
TestRCUpdate _rc_update;
DEFINE_PARAMETERS(
(ParamInt<px4::params::RC_MAP_FLTMODE>) _param_rc_map_fltmode,
(ParamInt<px4::params::RC_MAP_FLTM_BTN>) _param_rc_map_fltm_btn,
(ParamInt<px4::params::RC_MAP_RETURN_SW>) _param_rc_map_return_sw,
(ParamFloat<px4::params::RC_RETURN_TH>) _param_rc_return_th,
(ParamInt<px4::params::RC_MAP_PAY_SW>) _param_rc_map_pay_sw,
(ParamFloat<px4::params::RC_PAYLOAD_TH>) _param_rc_payload_th,
(ParamFloat<px4::params::RC_PAYLOAD_MIDTH>) _param_rc_payload_midth
)
};
TEST_F(RCUpdateTest, ModeSlotUnassigned)
{
// GIVEN: Default configuration with no assigned mode switch
EXPECT_EQ(_param_rc_map_fltmode.get(), 0);
// WHEN: we update the switches two times to pass the simple outlier protection
_rc_update.UpdateManualSwitches(0);
_rc_update.UpdateManualSwitches(0);
// THEN: we receive no mode slot
uORB::SubscriptionData<manual_control_switches_s> manual_control_switches_sub{ORB_ID(manual_control_switches)};
EXPECT_EQ(manual_control_switches_sub.get().mode_slot, 0); // manual_control_switches_s::MODE_SLOT_NONE
}
TEST_F(RCUpdateTest, ModeSlotSwitchAllValues)
{
checkModeSlotSwitch(-1.f, 1); // manual_control_switches_s::MODE_SLOT_1
checkModeSlotSwitch(-.5f, 2); // manual_control_switches_s::MODE_SLOT_2
checkModeSlotSwitch(-.1f, 3); // manual_control_switches_s::MODE_SLOT_3
checkModeSlotSwitch(0.f, 4); // manual_control_switches_s::MODE_SLOT_4
checkModeSlotSwitch(.5f, 5); // manual_control_switches_s::MODE_SLOT_5
checkModeSlotSwitch(1.f, 6); // manual_control_switches_s::MODE_SLOT_6
}
TEST_F(RCUpdateTest, ModeSlotButtonAllValues)
{
checkModeSlotButton(1, 1, -1.f, 0); // button not pressed -> manual_control_switches_s::MODE_SLOT_NONE
checkModeSlotButton(1, 1, 0.f, 0); // button not pressed over threshold -> manual_control_switches_s::MODE_SLOT_NONE
checkModeSlotButton(1, 1, 1.f, 1); // button 1 pressed -> manual_control_switches_s::MODE_SLOT_1
checkModeSlotButton(1, 2, 1.f, 0); // button 2 pressed but not configured -> manual_control_switches_s::MODE_SLOT_NONE
checkModeSlotButton(3, 2, 1.f, 2); // button 2 pressed -> manual_control_switches_s::MODE_SLOT_2
checkModeSlotButton(3, 3, 1.f, 0); // button 3 pressed but not configured -> manual_control_switches_s::MODE_SLOT_NONE
checkModeSlotButton(7, 3, 1.f, 3); // button 3 pressed -> manual_control_switches_s::MODE_SLOT_3
checkModeSlotButton(7, 4, 1.f, 0); // button 4 pressed but not configured -> manual_control_switches_s::MODE_SLOT_NONE
checkModeSlotButton(15, 4, 1.f, 4); // button 4 pressed -> manual_control_switches_s::MODE_SLOT_4
checkModeSlotButton(15, 5, 1.f, 0); // button 5 pressed but not configured -> manual_control_switches_s::MODE_SLOT_NONE
checkModeSlotButton(31, 5, 1.f, 5); // button 5 pressed -> manual_control_switches_s::MODE_SLOT_5
checkModeSlotButton(31, 6, 1.f, 0); // button 6 pressed but not configured -> manual_control_switches_s::MODE_SLOT_NONE
checkModeSlotButton(63, 6, 1.f, 6); // button 6 pressed -> manual_control_switches_s::MODE_SLOT_6
}
TEST_F(RCUpdateTest, ReturnSwitchUnassigned)
{
// GIVEN: Default configuration with no assigned return switch
EXPECT_EQ(_param_rc_map_return_sw.get(), 0);
// WHEN: we update the switches two times to pass the simple outlier protection
_rc_update.UpdateManualSwitches(0);
_rc_update.UpdateManualSwitches(0);
// THEN: we receive an unmapped return switch state
uORB::SubscriptionData<manual_control_switches_s> manual_control_switches_sub{ORB_ID(manual_control_switches)};
EXPECT_EQ(manual_control_switches_sub.get().return_switch, 0); // manual_control_switches_s::SWITCH_POS_NONE
}
TEST_F(RCUpdateTest, ReturnSwitchPositiveThresholds)
{
checkReturnSwitch(-1.f, 0.5f, 3); // Below threshold -> SWITCH_POS_OFF
checkReturnSwitch(0.f, 0.5f, 3); // On threshold -> SWITCH_POS_OFF
checkReturnSwitch(.001f, 0.5f, 1); // Slightly above threshold -> SWITCH_POS_ON
checkReturnSwitch(1.f, 0.5f, 1); // Above threshold -> SWITCH_POS_ON
checkReturnSwitch(-1.f, 0.75f, 3); // Below threshold -> SWITCH_POS_OFF
checkReturnSwitch(0.f, 0.75f, 3); // Below threshold -> SWITCH_POS_OFF
checkReturnSwitch(.5f, 0.75f, 3); // On threshold -> SWITCH_POS_OFF
checkReturnSwitch(.501f, 0.75f, 1); // Slightly above threshold -> SWITCH_POS_ON
checkReturnSwitch(1.f, 0.75f, 1); // Above threshold -> SWITCH_POS_ON
checkReturnSwitch(-1.f, 0.f, 3); // On minimum threshold -> SWITCH_POS_OFF
checkReturnSwitch(-.999f, 0.f, 1); // Slightly above minimum threshold -> SWITCH_POS_ON
checkReturnSwitch(1.f, 0.f, 1); // Above minimum threshold -> SWITCH_POS_ON
checkReturnSwitch(-1.f, 1.f, 3); // Below maximum threshold -> SWITCH_POS_OFF
checkReturnSwitch(1.f, 1.f, 3); // On maximum threshold -> SWITCH_POS_OFF
}
TEST_F(RCUpdateTest, ReturnSwitchNegativeThresholds)
{
checkReturnSwitch(1.f, -0.5f, 3); // Above threshold -> SWITCH_POS_OFF
checkReturnSwitch(0.f, -0.5f, 3); // On threshold -> SWITCH_POS_OFF
checkReturnSwitch(-.001f, -0.5f, 1); // Slightly below threshold -> SWITCH_POS_ON
checkReturnSwitch(-1.f, -0.5f, 1); // Below threshold -> SWITCH_POS_ON
checkReturnSwitch(1.f, -0.75f, 3); // Above threshold -> SWITCH_POS_OFF
checkReturnSwitch(.5f, -0.75f, 3); // On threshold -> SWITCH_POS_OFF
checkReturnSwitch(.499f, -0.75f, 1); // Slightly below threshold -> SWITCH_POS_ON
checkReturnSwitch(-1.f, -0.75f, 1); // Below threshold -> SWITCH_POS_ON
checkReturnSwitch(1.f, -1.f, 3); // On maximum threshold -> SWITCH_POS_OFF
checkReturnSwitch(.999f, -1.f, 1); // Slighly below maximum threshold -> SWITCH_POS_ON
checkReturnSwitch(-1.f, -1.f, 1); // Below minimum threshold -> SWITCH_POS_ON
checkReturnSwitch(1.f, -.001f, 3); // Above minimum threshold -> SWITCH_POS_OFF
checkReturnSwitch(-1.f, -.001f, 1); // Slightly below minimum threshold -> SWITCH_POS_OFF
}
TEST_F(RCUpdateTest, PayloadPower2WaySwitchPositiveThresholds)
{
checkPayloadPowerSwitch(-1.f, 0.5f, 0.5f, 3); // Below threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(0.f, 0.5f, 0.5f, 3); // On threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(.001f, 0.5f, 0.5f, 1); // Slightly above threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(1.f, 0.5f, 0.5f, 1); // Above threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(-1.f, 0.75f, 0.75f, 3); // Below threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(0.f, 0.75f, 0.75f, 3); // Below threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(.5f, 0.75f, 0.75f, 3); // On threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(.501f, 0.75f, 0.75f, 1); // Slightly above threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(1.f, 0.75f, 0.75f, 1); // Above threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(-1.f, 0.f, 0.f, 3); // On minimum threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(-.999f, 0.f, 0.f, 1); // Slightly above minimum threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(1.f, 0.f, 0.f, 1); // Above minimum threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(-1.f, 1.f, 1.f, 3); // Below maximum threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(1.f, 1.f, 1.f, 3); // On maximum threshold -> SWITCH_POS_OFF
}
TEST_F(RCUpdateTest, PayloadPower3WaySwitchPositiveThresholds)
{
checkPayloadPowerSwitch(-1.f, 0.75f, 0.25f, 3); // Below mid_threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(-0.5f, 0.75f, 0.25f, 3); // On mid_threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(-0.449f, 0.75f, 0.25f, 2); // Slightly above mid_threshold -> SWITCH_POS_MIDDLE
checkPayloadPowerSwitch(0.f, 0.75f, 0.25f, 2); // Between mid_threshold and on_threshold -> SWITCH_POS_MIDDLE
checkPayloadPowerSwitch(0.5f, 0.75f, 0.25f, 2); // On on_threshold -> SWITCH_POS_MIDDLE
checkPayloadPowerSwitch(0.501f, 0.75f, 0.25f, 1); // Slightly above on_threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(1.f, 0.75f, 0.25f, 1); // Above on_threshold -> SWITCH_POS_ON
}
TEST_F(RCUpdateTest, PayloadPower2WaySwitchNegativeThresholds)
{
checkPayloadPowerSwitch(1.f, -0.5f, -0.5f, 3); // Above threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(0.f, -0.5f, -0.5f, 3); // On threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(-.001f, -0.5f, -0.5f, 1); // Slightly below threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(-1.f, -0.5f, -0.5f, 1); // Below threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(1.f, -0.75f, -0.75f, 3); // Above threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(.5f, -0.75f, -0.75f, 3); // On threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(.499f, -0.75f, -0.75f, 1); // Slightly below threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(-1.f, -0.75f, -0.75f, 1); // Below threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(1.f, -1.f, -1.f, 3); // On maximum threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(.999f, -1.f, -1.f, 1); // Slighly below maximum threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(-1.f, -1.f, -1.f, 1); // Below minimum threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(1.f, -.001f, -.001f, 3); // Above minimum threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(-1.f, -.001f, -.001f, 1); // Slightly below minimum threshold -> SWITCH_POS_OFF
}
TEST_F(RCUpdateTest, PayloadPower3WaySwitchNegativeThresholds)
{
checkPayloadPowerSwitch(1.f, -0.75f, -0.25f, 3); // Above on_threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(0.5f, -0.75f, -0.25f, 3); // On on_threshold -> SWITCH_POS_OFF
checkPayloadPowerSwitch(0.449f, -0.75f, -0.25f, 2); // Slightly below on_threshold -> SWITCH_POS_MIDDLE
checkPayloadPowerSwitch(0.f, -0.75f, -0.25f, 2); // Between on_threshold and mid_threshold -> SWITCH_POS_MIDDLE
checkPayloadPowerSwitch(-0.5f, -0.75f, -0.25f, 2); // On mid_threshold -> SWITCH_POS_MIDDLE
checkPayloadPowerSwitch(-0.501f, -0.75f, -0.25f, 1); // Slightly above on_threshold -> SWITCH_POS_ON
checkPayloadPowerSwitch(-1.f, -0.75f, -0.25f, 1); // Above on_threshold -> SWITCH_POS_ON
}