UnloadToFinda: update unit tests

main
D.R.racer 2025-11-19 14:42:54 +01:00 committed by DRracer
parent 9e3b300b2e
commit e089be4ff8
4 changed files with 89 additions and 115 deletions

View File

@ -87,7 +87,10 @@ bool UnloadToFinda::Step() {
// we reached the end of move queue, but the FINDA didn't switch off // we reached the end of move queue, but the FINDA didn't switch off
// two possible causes - grinded filament or malfunctioning FINDA // two possible causes - grinded filament or malfunctioning FINDA
if (--maxTries) { if (--maxTries) {
Reset(maxTries); // try again // Ideally, the Idler shall rehome and then try again.
// That would auto-resolve errors caused by slipped or misaligned Idler
mi::idler.InvalidateHoming();
Reset(maxTries);
} else { } else {
state = FailedFINDA; state = FailedFINDA;
} }

View File

@ -79,19 +79,27 @@ void ForceReinitAllAutomata() {
mg::globals.Init(); mg::globals.Init();
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::AtPulley); mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::AtPulley);
} }
void HomeIdler() {
logic::NoCommand nc; // just a dummy instance which has an empty Step()
SimulateIdlerHoming(nc);
SimulateIdlerWaitForHomingValid(nc);
SimulateIdlerMoveToParkingPosition(nc);
}
void HomeSelector() {
logic::NoCommand nc; // just a dummy instance which has an empty Step()
SimulateSelectorHoming(nc);
SimulateSelectorWaitForHomingValid(nc);
SimulateSelectorWaitForReadyState(nc);
}
void HomeIdlerAndSelector() { void HomeIdlerAndSelector() {
mi::idler.InvalidateHoming(); mi::idler.InvalidateHoming();
ms::selector.InvalidateHoming(); ms::selector.InvalidateHoming();
logic::NoCommand nc; // just a dummy instance which has an empty Step()
SimulateIdlerHoming(nc); HomeIdler();
SimulateIdlerWaitForHomingValid(nc);
SimulateIdlerMoveToParkingPosition(nc);
SimulateSelectorHoming(nc); HomeSelector();
SimulateSelectorWaitForHomingValid(nc);
SimulateSelectorWaitForReadyState(nc);
} }
bool EnsureActiveSlotIndex(uint8_t slot, mg::FilamentLoadState loadState) { bool EnsureActiveSlotIndex(uint8_t slot, mg::FilamentLoadState loadState) {

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "../../../../src/logic/command_base.h" #include "../../../../src/logic/command_base.h"
#include "../../../../src/modules/globals.h" #include "../../../../src/modules/globals.h"
#include "../../../../src/modules/idler.h"
extern void main_loop(); extern void main_loop();
extern void ForceReinitAllAutomata(); extern void ForceReinitAllAutomata();
@ -44,3 +45,21 @@ static constexpr uint32_t selectorMoveMaxSteps = 40000UL;
void HomeIdlerAndSelector(); void HomeIdlerAndSelector();
void SimulateErrDisengagingIdler(logic::CommandBase &cb, ErrorCode deferredEC); void SimulateErrDisengagingIdler(logic::CommandBase &cb, ErrorCode deferredEC);
template <typename T>
bool SimulateEngageIdlerFully(T &cb) {
return WhileCondition(
cb,
[&](uint32_t) { return !mi::idler.Engaged(); },
5000);
}
template <typename T>
bool SimulateEngageIdlerPartially(T &cb) {
return WhileCondition(
cb,
[&](uint32_t) { return !mi::idler.PartiallyDisengaged(); },
5000);
}
void HomeIdler();

View File

@ -12,10 +12,12 @@
#include "../../../../src/modules/motion.h" #include "../../../../src/modules/motion.h"
#include "../../../../src/modules/permanent_storage.h" #include "../../../../src/modules/permanent_storage.h"
#include "../../../../src/modules/selector.h" #include "../../../../src/modules/selector.h"
#include "../../../../src/modules/timebase.h"
#include "../../../../src/logic/unload_to_finda.h" #include "../../../../src/logic/unload_to_finda.h"
#include "../../modules/stubs/stub_adc.h" #include "../../modules/stubs/stub_adc.h"
#include "../../modules/stubs/stub_timebase.h"
#include "../stubs/main_loop_stub.h" #include "../stubs/main_loop_stub.h"
#include "../stubs/stub_motion.h" #include "../stubs/stub_motion.h"
@ -24,7 +26,7 @@ using namespace std::placeholders;
namespace ha = hal::adc; namespace ha = hal::adc;
TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") { void UnloadToFindaCommonSetup(logic::UnloadToFinda &ff, uint8_t retryAttempts) {
ForceReinitAllAutomata(); ForceReinitAllAutomata();
REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley)); REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley));
@ -35,10 +37,8 @@ TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") {
// and MMU "thinks" it has the filament loaded // and MMU "thinks" it has the filament loaded
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle); mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle);
logic::UnloadToFinda ff;
// restart the automaton - just 1 attempt // restart the automaton - just 1 attempt
ff.Reset(1); ff.Reset(retryAttempts);
REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler); REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler);
@ -47,12 +47,10 @@ TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") {
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::IntermediateSlotPosition(0).v); CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::IntermediateSlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v); CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
// engaging idler partially REQUIRE(SimulateEngageIdlerPartially(ff));
REQUIRE(WhileCondition( }
ff,
[&](uint32_t) { return !mi::idler.PartiallyDisengaged(); },
5000));
void UnloadToFindaCommonTurnOffFSensor(logic::UnloadToFinda &ff) {
// turn off fsensor - the printer freed the filament from the gears // turn off fsensor - the printer freed the filament from the gears
SetFSensorStateAndDebounce(false); SetFSensorStateAndDebounce(false);
@ -63,14 +61,17 @@ TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") {
CHECK(mm::axes[mm::Pulley].enabled == true); CHECK(mm::axes[mm::Pulley].enabled == true);
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v); CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
// engaging idler fully REQUIRE(SimulateEngageIdlerFully(ff));
REQUIRE(WhileCondition(
ff,
[&](uint32_t) { return !mi::idler.Engaged(); },
5000));
// now pulling the filament until finda triggers // now pulling the filament until finda triggers
REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA); REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA);
}
TEST_CASE("unload_to_finda::regular_unload", "[unload_to_finda]") {
logic::UnloadToFinda ff;
UnloadToFindaCommonSetup(ff, 1);
UnloadToFindaCommonTurnOffFSensor(ff);
REQUIRE(WhileCondition(ff, std::bind(SimulateUnloadToFINDA, _1, 10, 1000), 1100)); REQUIRE(WhileCondition(ff, std::bind(SimulateUnloadToFINDA, _1, 10, 1000), 1100));
REQUIRE(ff.State() == logic::UnloadToFinda::OK); REQUIRE(ff.State() == logic::UnloadToFinda::OK);
@ -92,37 +93,9 @@ TEST_CASE("unload_to_finda::no_sense_FINDA_upon_start", "[unload_to_finda]") {
} }
TEST_CASE("unload_to_finda::unload_without_FINDA_trigger", "[unload_to_finda]") { TEST_CASE("unload_to_finda::unload_without_FINDA_trigger", "[unload_to_finda]") {
ForceReinitAllAutomata();
REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley));
// we need finda ON
SetFINDAStateAndDebounce(true);
// fsensor should be ON
SetFSensorStateAndDebounce(true);
// and MMU "thinks" it has the filament loaded
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle);
logic::UnloadToFinda ff; logic::UnloadToFinda ff;
UnloadToFindaCommonSetup(ff, 1);
// restart the automaton - just 1 attempt UnloadToFindaCommonTurnOffFSensor(ff);
ff.Reset(1);
REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler);
// it should have instructed the selector and idler to move to slot 1
// check if the idler and selector have the right command
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
CHECK(mm::axes[mm::Idler].enabled == true);
// engaging idler
REQUIRE(WhileCondition(
ff,
[&](uint32_t) { return !mi::idler.Engaged(); },
5000));
// now pulling the filament until finda triggers
REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA);
// no changes to FINDA during unload - we'll pretend it never triggers // no changes to FINDA during unload - we'll pretend it never triggers
// but set FSensor correctly // but set FSensor correctly
@ -134,96 +107,67 @@ TEST_CASE("unload_to_finda::unload_without_FINDA_trigger", "[unload_to_finda]")
} }
TEST_CASE("unload_to_finda::unload_without_FSensor_trigger", "[unload_to_finda]") { TEST_CASE("unload_to_finda::unload_without_FSensor_trigger", "[unload_to_finda]") {
ForceReinitAllAutomata();
REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley));
// we need finda ON
SetFINDAStateAndDebounce(true);
// fsensor should be ON
SetFSensorStateAndDebounce(true);
// and MMU "thinks" it has the filament loaded
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle);
logic::UnloadToFinda ff; logic::UnloadToFinda ff;
UnloadToFindaCommonSetup(ff, 1);
// restart the automaton - just 1 attempt
ff.Reset(1);
REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler);
// it should have instructed the selector and idler to move to slot 1
// check if the idler and selector have the right command
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v);
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
CHECK(mm::axes[mm::Idler].enabled == true);
// engaging idler
REQUIRE(WhileCondition(
ff,
[&](uint32_t) { return !mi::idler.Engaged(); },
5000));
// now pulling the filament until finda triggers
REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA);
// no changes to FSensor during unload - we'll pretend it never triggers // no changes to FSensor during unload - we'll pretend it never triggers
// but set FINDA correctly // time-out in 4 seconds
REQUIRE(WhileCondition(ff, std::bind(SimulateUnloadToFINDA, _1, 150000, 10000), 50000)); mt::IncMillis(4000);
main_loop();
ff.Step();
// no pulling actually starts, because the fsensor didn't turn off and the time-out elapsed
REQUIRE(ff.State() == logic::UnloadToFinda::FailedFSensor); REQUIRE(ff.State() == logic::UnloadToFinda::FailedFSensor);
REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InSelector); REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InNozzle);
} }
TEST_CASE("unload_to_finda::unload_repeated", "[unload_to_finda]") { TEST_CASE("unload_to_finda::unload_repeated", "[unload_to_finda]") {
ForceReinitAllAutomata();
REQUIRE(EnsureActiveSlotIndex(0, mg::FilamentLoadState::AtPulley));
// we need finda ON
SetFINDAStateAndDebounce(true);
// fsensor should be ON
SetFSensorStateAndDebounce(true);
// and MMU "thinks" it has the filament loaded
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle);
logic::UnloadToFinda ff; logic::UnloadToFinda ff;
UnloadToFindaCommonSetup(ff, 2);
// restart the automaton - 2 attempts UnloadToFindaCommonTurnOffFSensor(ff);
ff.Reset(2);
REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler); // remember raw Pulley pos for tweaking the steps below
// because a 20mm (config::fsensorToNozzleAvoidGrindUnload)
// it should have instructed the selector and idler to move to slot 1 // move is being executed while the Idler is fully engaging
// check if the idler and selector have the right command // It is roughly -90 steps
CHECK(mm::AxisNearestTargetPos(mm::Idler) == mi::Idler::SlotPosition(0).v); // int32_t pulleySteppedAlready = mm::axes[config::Pulley].pos;
CHECK(mm::AxisNearestTargetPos(mm::Selector) == ms::Selector::SlotPosition(0).v);
CHECK(mm::axes[mm::Idler].enabled == true);
// engaging idler
REQUIRE(WhileCondition(
ff,
[&](uint32_t) { return !mi::idler.Engaged(); },
5000));
// now pulling the filament until finda triggers
REQUIRE(ff.State() == logic::UnloadToFinda::WaitingForFINDA);
// no changes to FINDA during unload - we'll pretend it never triggers // no changes to FINDA during unload - we'll pretend it never triggers
// but set FSensor correctly // but set FSensor correctly
// In this case it is vital to correctly compute the amount of steps // In this case it is vital to correctly compute the amount of steps
// to make the unload state machine restart after the 1st attempt // to make the unload state machine restart after the 1st attempt
uint32_t unlSteps = 1 + mm::unitToSteps<mm::P_pos_t>(config::maximumBowdenLength + config::feedToFinda + config::filamentMinLoadedToMMU); // The number of steps must be more than what the state machine expects for FINDA to trigger.
uint32_t unlSteps = 1 + mm::unitToSteps<mm::P_pos_t>(
// standard fast move distance
config::maximumBowdenLength + config::feedToFinda + config::filamentMinLoadedToMMU
// slow start move distance
+ config::fsensorToNozzleAvoidGrindUnload);
// compensation
// + pulleySteppedAlready;
REQUIRE_FALSE(WhileCondition(ff, std::bind(SimulateUnloadToFINDA, _1, 10, 150000), unlSteps)); REQUIRE_FALSE(WhileCondition(ff, std::bind(SimulateUnloadToFINDA, _1, 10, 150000), unlSteps));
main_loop(); main_loop();
ff.Step(); ff.Step();
REQUIRE_FALSE(mi::idler.HomingValid());
REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler); REQUIRE(ff.State() == logic::UnloadToFinda::EngagingIdler);
REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InSelector); REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InSelector);
HomeIdler();
main_loop();
ff.Step();
REQUIRE(ff.State() == logic::UnloadToFinda::UnloadingToFinda);
SimulateEngageIdlerPartially(ff);
main_loop(); main_loop();
ff.Step(); ff.Step();
REQUIRE(ff.State() == logic::UnloadToFinda::UnloadingToFinda); SimulateEngageIdlerFully(ff);
REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InSelector); REQUIRE(mg::globals.FilamentLoaded() == mg::FilamentLoadState::InSelector);
// make arbitrary amount of steps // make arbitrary amount of steps